diff --git a/.dagger/modules/e2e/dagger.json b/.dagger/modules/e2e/dagger.json index a8bdbc1..8c6555d 100644 --- a/.dagger/modules/e2e/dagger.json +++ b/.dagger/modules/e2e/dagger.json @@ -8,6 +8,11 @@ { "name": "python-sdk", "source": "../../.." + }, + { + "name": "sdk-sdk", + "source": "github.com/dagger/sdk-sdk", + "pin": "e1747f4b6221fa24da080701e027243e0cc5fa33" } ] } diff --git a/.dagger/modules/e2e/fixtures/runtime/app/.gitattributes b/.dagger/modules/e2e/fixtures/runtime/app/.gitattributes new file mode 100644 index 0000000..8274184 --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/.gitattributes @@ -0,0 +1 @@ +/sdk/** linguist-generated diff --git a/.dagger/modules/e2e/fixtures/runtime/app/.gitignore b/.dagger/modules/e2e/fixtures/runtime/app/.gitignore new file mode 100644 index 0000000..6fac159 --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/.gitignore @@ -0,0 +1,3 @@ +/.venv +/**/__pycache__ +/.env diff --git a/.dagger/modules/e2e/fixtures/runtime/app/dagger-module.toml b/.dagger/modules/e2e/fixtures/runtime/app/dagger-module.toml new file mode 100644 index 0000000..aea8525 --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/dagger-module.toml @@ -0,0 +1,5 @@ +name = "runtime-app" +engineVersion = "v1.0.0-0" + +[runtime] +source = "../../../../../../runtime" diff --git a/.dagger/modules/e2e/fixtures/runtime/app/pyproject.toml b/.dagger/modules/e2e/fixtures/runtime/app/pyproject.toml new file mode 100644 index 0000000..71cb7e6 --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/pyproject.toml @@ -0,0 +1,12 @@ +[project] +name = "runtime-app" +version = "0.1.0" +requires-python = ">=3.14" +dependencies = ["dagger-io"] + +[build-system] +requires = ["uv_build>=0.8.4,<0.12.0"] +build-backend = "uv_build" + +[tool.uv.sources] +dagger-io = { path = "sdk", editable = true } diff --git a/.dagger/modules/e2e/fixtures/runtime/app/sdk/LICENSE b/.dagger/modules/e2e/fixtures/runtime/app/sdk/LICENSE new file mode 100644 index 0000000..17b2ba2 --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/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/.dagger/modules/e2e/fixtures/runtime/app/sdk/README.md b/.dagger/modules/e2e/fixtures/runtime/app/sdk/README.md new file mode 100644 index 0000000..7b198bb --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/sdk/README.md @@ -0,0 +1,158 @@ +# Dagger Python SDK + +> [!NOTE] +> This tree was taken from `dagger/dagger` at commit +> `501b57e0476dee5881b99a064c3c04173134ecc7`, path `sdk/python`. It is the +> client library and code generator that the runtime above it vendors into the +> modules it builds. `dagger/dagger` keeps its own copy to serve legacy +> `dagger.json` modules; the two are expected to diverge from here. +> Sphinx docs and changelog history were not carried over. Of the test suites, +> only `tests/codegen` and `tests/mod` came along; `tests/client` and +> `tests/provisioning` did not, so the vendored connection and provisioning +> code is carried without its tests. + +[![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 (run from this directory, `./sdk/python`, or adjust export path accordingly): + +```shell +dagger call -m ../../.dagger/modules/python-client-dev dev-container with-exec --args uv,lock,-U directory --path . export --path . +``` + +Update a single pinned development dependency (run from this directory, `./sdk/python`, or adjust export path accordingly): + +```shell +dagger call -m ../../.dagger/modules/python-client-dev dev-container with-exec --args uv,lock,-P, directory --path . export --path . +``` + +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/.dagger/modules/e2e/fixtures/runtime/app/sdk/pyproject.toml b/.dagger/modules/e2e/fixtures/runtime/app/sdk/pyproject.toml new file mode 100644 index 0000000..ad73483 --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/sdk/pyproject.toml @@ -0,0 +1,77 @@ +[build-system] +requires = ["uv_build>=0.8.4,<0.12.0"] +build-backend = "uv_build" + +[project] +name = "dagger-io" +version = "0.0.0" +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", + "Programming Language :: Python :: 3.14", + "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]>=4.0", + # Avoid a broken release with only cp310 wheels. + "yarl!=1.24.1", + "httpcore>=1.0.8", + "beartype>=0.22.0", + "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", + "opentelemetry-instrumentation-logging>=0.54b1", + "exceptiongroup>=1.3.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.build-backend] +module-name = "dagger" +source-include = ["tests/**", "LICENSE"] + +[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/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/__init__.py b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/__init__.py new file mode 100644 index 0000000..c82edfe --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/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/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/_engine/_version.py b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/_engine/_version.py new file mode 100644 index 0000000..9187ccd --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/_engine/_version.py @@ -0,0 +1,3 @@ +# Code generated by dagger. DO NOT EDIT. + +CLI_VERSION = "1.0.0-beta.10" diff --git a/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/_exceptions.py b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/_exceptions.py new file mode 100644 index 0000000..69849a1 --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/_exceptions.py @@ -0,0 +1,180 @@ +import dataclasses +from typing import Any + +import cattrs +import gql +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: + msg = "Failed to establish client connection to the Dagger session" + if sup := super().__str__(): + return f"{msg}: {sup}" + return msg + + +class TransportError(ClientError): + """Error processing request/response during query execution.""" + + +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], request: gql.GraphQLRequest): + if not errors: + msg = "Errors list is empty" + raise ValueError(msg) + super().__init__(*errors) + self.errors: list[QueryErrorValue] = errors + self.request = request + self.query = request.document + + @property + def error(self) -> QueryErrorValue: + return self.errors[0] + + def __str__(self) -> str: + return str(self.error) + + 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, request: gql.GraphQLRequest): + """Create instance from a gql exception.""" + try: + errors = cattrs.structure(exc.errors, list[QueryErrorValue]) + except (TypeError, KeyError, ValueError): + return None + return QueryError(errors, request) 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) + + ext = self.error.extensions + self.command = ext["cmd"] + self.message = self.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", + "InvalidQueryError", + "QueryError", + "TransportError", + "VersionMismatch", +] diff --git a/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/_managers.py b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/_managers.py new file mode 100644 index 0000000..3e3a9c6 --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/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/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/client/__init__.py b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/client/_config.py b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/client/_config.py new file mode 100644 index 0000000..73dceb8 --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/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/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/client/_connection.py b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/client/_connection.py new file mode 100644 index 0000000..6640389 --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/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/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/client/_core.py b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/client/_core.py new file mode 100644 index 0000000..7e0fd38 --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/client/_core.py @@ -0,0 +1,362 @@ +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 exceptiongroup +import gql +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, + DSLInlineFragment, + DSLQuery, + DSLSchema, + DSLSelectable, + DSLType, + dsl_gql, +) +from gql.transport.exceptions import ( + TransportClosed, + TransportConnectionFailed, + TransportProtocolError, + TransportQueryError, + TransportServerError, +) +from typing_extensions import TypeForm + +from dagger import ( + DaggerError, + 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) + # When set, children are wrapped in an inline fragment on this type: + # field(args) { ... on inline_type { children } } + inline_type: str | None = None + + def to_dsl(self, schema: DSLSchema) -> DSLField: + type_: DSLType = getattr(schema, self.type_name) + field_ = getattr(type_, self.name)(**self.args) + if self.children: + child_fields = { + name: child.to_dsl(schema) for name, child in self.children.items() + } + if self.inline_type is not None: + frag_type: DSLType = getattr(schema, self.inline_type) + inline = DSLInlineFragment().on(frag_type).select(**child_fields) + field_ = field_.select(inline) + else: + field_ = field_.select(**child_fields) + 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 != 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": + """Load an object by its ID via node(id:) with an inline fragment.""" + ctx = dataclasses.replace(self, selections=collections.deque()) + node_field = Field( + type_name="Query", + name="node", + args={"id": id_value}, + inline_type=type_name, + ) + selections = ctx.selections.copy() + selections.append(node_field) + return dataclasses.replace(ctx, selections=selections) + + 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. + try: + return root.to_dsl(DSLSchema(await self.conn.session.get_schema())) + except (graphql.GraphQLError, AttributeError, TypeError) as e: + logger.exception("GraphQL query builder failed to build query") + msg = ( + "Failed to build GraphQL query, probably due to a schema validation " + "issue. Please file a bug report because anything that could " + "fail to validate at this point should really happen sooner. " + "See Python logs for more details." + ) + raise InvalidQueryError(msg) from e + + async def request(self) -> gql.GraphQLRequest: + 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() + request = await self.request() + + try: + result = await self.conn.session.execute(request) + + 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 TransportConnectionFailed as e: + if not (msg := str(e)): + match e.__cause__: + case httpx.TimeoutException(): + msg = ( + "Request timed out. Try setting a higher timeout value " + "for this connection." + ) + case _: + msg = "Failed to execute request" + + raise TransportError(msg) from e + + except TransportQueryError as e: + if error := _query_error_from_transport(e, request): + 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]) + + gql_name = element_type._graphql_name() # noqa: SLF001 + return [element_type(ctx.select_id(gql_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._graphql_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) + + def handle_group_err(self, grp: exceptiongroup.BaseExceptionGroup): + """Handle exception group errors.""" + # just re-raise the first one + for exc in grp.exceptions: + raise exc from None + + 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 + with exceptiongroup.catch( + {(graphql.GraphQLError, DaggerError): self.handle_group_err} + ): + 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/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/client/_guards.py b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/client/_guards.py new file mode 100644 index 0000000..e931117 --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/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/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/client/_session.py b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/client/_session.py new file mode 100644 index 0000000..b32b074 --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/client/_session.py @@ -0,0 +1,259 @@ +import contextlib +import logging +import os +from dataclasses import dataclass, field +from typing import Any + +import gql +import graphql +import httpx +from gql.client import AsyncClientSession +from gql.client import Client as GraphQLClient +from gql.transport.exceptions import ( + TransportConnectionFailed, + 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 the schema for DSL query building, but don't + # validate queries client-side. The server validates with + # a corrected PossibleFragmentSpreads rule that handles + # interface-implements-interface; the graphql-core library + # used here does not, causing false rejections for + # `... on SomeIface` inside `node(id:)` when the interface + # has no concrete implementors in this schema view. + fetch_schema_from_transport=True, + # We're using the timeout from the httpx transport. + execute_timeout=None, + ) + # Disable client-side query validation. See comment above. + client.validate = lambda _request: None # type: ignore[method-assign] + + 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 TransportConnectionFailed as e: + raise ClientConnectionError(str(e)) 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: gql.GraphQLRequest) -> 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/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/client/base.py b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/client/base.py new file mode 100644 index 0000000..2c6daaa --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/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/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/client/gen.py b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/client/gen.py new file mode 100644 index 0000000..debb1e9 --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/client/gen.py @@ -0,0 +1,14988 @@ +# Code generated by dagger. DO NOT EDIT. + +import warnings # noqa: F401 +from collections.abc import Callable +from dataclasses import dataclass +from typing import Protocol, runtime_checkable + +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 JSON(Scalar): + """An arbitrary JSON-encoded value.""" + + +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 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 ChangesetMergeConflict(Enum): + """Strategy to use when merging changesets with conflicting + changes.""" + + FAIL = 'FAIL' + """Attempt the merge and fail if git merge fails due to conflicts""" + + FAIL_EARLY = 'FAIL_EARLY' + """Fail before attempting merge if file-level conflicts are detected""" + + LEAVE_CONFLICT_MARKERS = 'LEAVE_CONFLICT_MARKERS' + """Let git create conflict markers in files. For modify/delete conflicts, keeps the modified version. Fails on binary conflicts.""" + + PREFER_OURS = 'PREFER_OURS' + """The conflict is resolved by applying the version of the calling changeset""" + + PREFER_THEIRS = 'PREFER_THEIRS' + """The conflict is resolved by applying the version of the other changeset""" + + +class ChangesetsMergeConflict(Enum): + """Strategy to use when merging multiple changesets with git octopus + merge.""" + + FAIL = 'FAIL' + """Attempt the octopus merge and fail if git merge fails due to conflicts""" + + FAIL_EARLY = 'FAIL_EARLY' + """Fail before attempting merge if file-level conflicts are detected between any changesets""" + + +class DiffStatKind(Enum): + """The type of change for a diff stat entry.""" + + ADDED = 'ADDED' + """A file or directory was added.""" + + MODIFIED = 'MODIFIED' + """A file was modified.""" + + REMOVED = 'REMOVED' + """A file or directory was removed.""" + + RENAMED = 'RENAMED' + """A file was renamed.""" + + +class ExistsType(Enum): + """File type.""" + + DIRECTORY_TYPE = 'DIRECTORY_TYPE' + """Tests path is a directory""" + + REGULAR_TYPE = 'REGULAR_TYPE' + """Tests path is a regular file""" + + SYMLINK_TYPE = 'SYMLINK_TYPE' + """Tests path is a symlink""" + + +class FileType(Enum): + """File type.""" + + DIRECTORY = 'DIRECTORY' + """directory file type""" + DIRECTORY_TYPE = 'DIRECTORY' + """directory file type""" + + REGULAR = 'REGULAR' + """regular file type""" + REGULAR_TYPE = 'REGULAR' + """regular file type""" + + SYMLINK = 'SYMLINK' + """symlink file type""" + SYMLINK_TYPE = 'SYMLINK' + """symlink file type""" + + UNKNOWN = 'UNKNOWN' + """unknown file type""" + + +class FunctionCachePolicy(Enum): + """The behavior configured for function result caching.""" + + Default = 'Default' + + Never = 'Never' + + PerSession = 'PerSession' + + +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.""" + + DockerMediaTypes = 'DockerMediaTypes' + DOCKER = 'DockerMediaTypes' + + OCIMediaTypes = 'OCIMediaTypes' + OCI = 'OCIMediaTypes' + + +class LLMContentBlockKind(Enum): + """The kind of content in a message block.""" + + TEXT = 'TEXT' + """Plain text content.""" + + THINKING = 'THINKING' + """Model thinking/reasoning content (e.g. Anthropic extended thinking).""" + + TOOL_CALL = 'TOOL_CALL' + """A tool/function call from the model.""" + + TOOL_RESULT = 'TOOL_RESULT' + """A tool/function result.""" + + +class LLMMessageRole(Enum): + """The role that generated a message.""" + + ASSISTANT = 'ASSISTANT' + """A reply from the model.""" + + SYSTEM = 'SYSTEM' + """A system prompt.""" + + USER = 'USER' + """A user prompt or tool response.""" + + +class ModuleSourceExperimentalFeature(Enum): + """Experimental features of a module""" + + SELF_CALLS = 'SELF_CALLS' + """Self calls""" + + +class ModuleSourceKind(Enum): + """The kind of module source.""" + + DIR_SOURCE = 'DIR_SOURCE' + DIR = 'DIR_SOURCE' + + GIT_SOURCE = 'GIT_SOURCE' + GIT = 'GIT_SOURCE' + + LOCAL_SOURCE = 'LOCAL_SOURCE' + LOCAL = 'LOCAL_SOURCE' + + +class NetworkProtocol(Enum): + """Transport layer network protocol associated to a port.""" + + TCP = 'TCP' + + UDP = 'UDP' + + +class PatchConflict(Enum): + """How to handle patch hunks that no longer apply to the target + content.""" + + FAIL = 'FAIL' + """Fail the operation if any part of the patch does not apply.""" + + LEAVE_CONFLICT_MARKERS = 'LEAVE_CONFLICT_MARKERS' + """Apply the hunks that fit and insert conflict markers where hunks no longer match, instead of failing.""" + + +class RegistryProtocol(Enum): + """Transport protocol to use for registry operations.""" + + HTTP = 'HTTP' + + HTTPS = 'HTTPS' + + +class ReturnType(Enum): + """Expected return type of an execution""" + + ANY = 'ANY' + """Any execution (exit codes 0-127 and 192-255)""" + + FAILURE = 'FAILURE' + """A failed execution (exit codes 1-127 and 192-255)""" + + SUCCESS = 'SUCCESS' + """A successful execution (exit code 0)""" + + +class TypeDefKind(Enum): + """Distinguishes the different kinds of TypeDefs.""" + + BOOLEAN_KIND = 'BOOLEAN_KIND' + """A boolean value.""" + BOOLEAN = 'BOOLEAN_KIND' + """A boolean value.""" + + ENUM_KIND = 'ENUM_KIND' + """A GraphQL enum type and its values + + Always paired with an EnumTypeDef. + """ + ENUM = 'ENUM_KIND' + """A GraphQL enum type and its values + + Always paired with an EnumTypeDef. + """ + + FLOAT_KIND = 'FLOAT_KIND' + """A float value.""" + FLOAT = 'FLOAT_KIND' + """A float value.""" + + INPUT_KIND = 'INPUT_KIND' + """A graphql input type, used only when representing the core API via TypeDefs.""" + INPUT = 'INPUT_KIND' + """A graphql input type, used only when representing the core API via TypeDefs.""" + + INTEGER_KIND = 'INTEGER_KIND' + """An integer value.""" + INTEGER = 'INTEGER_KIND' + """An integer value.""" + + INTERFACE_KIND = 'INTERFACE_KIND' + """Always paired with an InterfaceTypeDef. + + A named type of functions that can be matched+implemented by other objects+interfaces. + """ + INTERFACE = 'INTERFACE_KIND' + """Always paired with an InterfaceTypeDef. + + A named type of functions that can be matched+implemented by other objects+interfaces. + """ + + LIST_KIND = 'LIST_KIND' + """Always paired with a ListTypeDef. + + A list of values all having the same type. + """ + LIST = 'LIST_KIND' + """Always paired with a ListTypeDef. + + A list of values all having the same type. + """ + + OBJECT_KIND = 'OBJECT_KIND' + """Always paired with an ObjectTypeDef. + + A named type defined in the GraphQL schema, with fields and functions. + """ + OBJECT = 'OBJECT_KIND' + """Always paired with an ObjectTypeDef. + + A named type defined in the GraphQL schema, with fields and functions. + """ + + SCALAR_KIND = 'SCALAR_KIND' + """A scalar value of any basic kind.""" + SCALAR = 'SCALAR_KIND' + """A scalar value of any basic kind.""" + + STRING_KIND = 'STRING_KIND' + """A string value.""" + STRING = 'STRING_KIND' + """A string value.""" + + 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. + """ + 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. + """ + + +@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 LLMContentBlockInput(Input): + """A content block within an LLM message.""" + + kind: LLMContentBlockKind + """The kind of content block.""" + + arguments: JSON | None = None + """The arguments to pass to the tool (for TOOL_CALL kind).""" + + call_id: str | None = '' + """The unique ID of a tool call (for TOOL_CALL or TOOL_RESULT kinds).""" + + errored: bool | None = False + """Whether the tool call resulted in an error (for TOOL_RESULT kind).""" + + signature: str | None = '' + """Provider-specific opaque data (e.g. Anthropic thinking signature).""" + + text: str | None = '' + """Text content (for TEXT, THINKING, or TOOL_RESULT kinds).""" + + tool_name: str | None = '' + """The name of the tool to call (for TOOL_CALL kind).""" + + +@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.""" + + +@runtime_checkable +class Exportable(Protocol): + """An object that can be exported to the host. Calling export writes + the object to a path on the host filesystem and returns the path that + was written. """ + + async def export(self, path: str) -> str: + ... + + +@typecheck +class _ExportableClient(Type): + """Concrete client for Exportable interface.""" + + @classmethod + def _graphql_name(cls) -> str: + return "Exportable" + + async def export(self, path: str) -> str: + """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), + ] + _ctx = self._select("export", _args) + return await _ctx.execute(str) + + async def id(self) -> str: + """Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + +@runtime_checkable +class Node(Protocol): + """An object with a globally unique ID.""" + + +@typecheck +class _NodeClient(Type): + """Concrete client for Node interface.""" + + @classmethod + def _graphql_name(cls) -> str: + return "Node" + + async def id(self) -> str: + """Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + +@runtime_checkable +class Syncer(Protocol): + """An object that can be force-evaluated. Calling sync ensures that + the object's entire dependency DAG has been evaluated, returning the + object's ID once complete. """ + + async def sync(self) -> Self: + ... + + +@typecheck +class _SyncerClient(Type): + """Concrete client for Syncer interface.""" + + @classmethod + def _graphql_name(cls) -> str: + return "Syncer" + + async def id(self) -> str: + """Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def sync(self) -> Self: + """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 Address(Type): + """A standardized address to load containers, directories, secrets, + and other object types. Address format depends on the type, and is + validated at type selection.""" + + def container(self) -> "Container": + """Load a container from the address.""" + _args: list[Arg] = [] + _ctx = self._select("container", _args) + return Container(_ctx) + + def directory(self, *, exclude: list[str] | None = None, include: list[str] | None = None, gitignore: bool | None = False, no_cache: bool | None = False,) -> "Directory": + """Load a directory from the address.""" + _args = [ + Arg("exclude", [] if exclude is None else exclude, []), + Arg("include", [] if include is None else include, []), + Arg("gitignore", gitignore, False), + Arg("noCache", no_cache, False), + ] + _ctx = self._select("directory", _args) + return Directory(_ctx) + + def file(self, *, exclude: list[str] | None = None, include: list[str] | None = None, gitignore: bool | None = False, no_cache: bool | None = False,) -> "File": + """Load a file from the address.""" + _args = [ + Arg("exclude", [] if exclude is None else exclude, []), + Arg("include", [] if include is None else include, []), + Arg("gitignore", gitignore, False), + Arg("noCache", no_cache, False), + ] + _ctx = self._select("file", _args) + return File(_ctx) + + def git_ref(self) -> "GitRef": + """Load a git ref (branch, tag or commit) from the address.""" + _args: list[Arg] = [] + _ctx = self._select("gitRef", _args) + return GitRef(_ctx) + + def git_repository(self) -> "GitRepository": + """Load a git repository from the address.""" + _args: list[Arg] = [] + _ctx = self._select("gitRepository", _args) + return GitRepository(_ctx) + + async def id(self) -> str: + """A unique identifier for this Address. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + def secret(self) -> "Secret": + """Load a secret from the address.""" + _args: list[Arg] = [] + _ctx = self._select("secret", _args) + return Secret(_ctx) + + def service(self) -> "Service": + """Load a service from the address.""" + _args: list[Arg] = [] + _ctx = self._select("service", _args) + return Service(_ctx) + + def socket(self) -> "Socket": + """Load a local socket from the address.""" + _args: list[Arg] = [] + _ctx = self._select("socket", _args) + return Socket(_ctx) + + async def value(self) -> str: + """The address 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 Agent(Type): + + async def description(self) -> str: + """The description of the agent + + 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) -> str: + """A unique identifier for this Agent. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def name(self) -> str: + """Return the fully qualified name of the agent + + 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 original_module(self) -> "Module": + """The original module in which the agent has been defined""" + _args: list[Arg] = [] + _ctx = self._select("originalModule", _args) + return Module(_ctx) + + async def path(self) -> list[str]: + """The path of the agent within its module + + 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("path", _args) + return await _ctx.execute(list[str]) + + +@typecheck +class AgentGroup(Type): + + def compose(self, *, base: "LLM | None" = None) -> "LLM": + """Compose all selected agent middlewares onto a base LLM, in + alphabetical module:fn order, and return the composed LLM. + + Parameters + ---------- + base: + The base LLM to compose onto. Defaults to a fresh workspace-bound + LLM. + """ + _args = [ + Arg("base", base, None), + ] + _ctx = self._select("compose", _args) + return LLM(_ctx) + + async def id(self) -> str: + """A unique identifier for this AgentGroup. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def list_(self) -> list[Agent]: + """Return a list of individual agents and their details""" + _args: list[Arg] = [] + _ctx = self._select("list", _args) + return await _ctx.execute_object_list(Agent) + + +@typecheck +class CacheVolume(Type): + """A directory whose contents persist across runs.""" + + async def id(self) -> str: + """A unique identifier for this CacheVolume. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + +@typecheck +class Changeset(Type): + """A comparison between two directories representing changes that can + be applied.""" + + async def added_paths(self) -> list[str]: + """Files and directories that were added in the newer directory. + + 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("addedPaths", _args) + return await _ctx.execute(list[str]) + + def after(self) -> "Directory": + """The newer/upper snapshot.""" + _args: list[Arg] = [] + _ctx = self._select("after", _args) + return Directory(_ctx) + + def as_patch(self) -> "File": + """Return a Git-compatible patch of the changes""" + _args: list[Arg] = [] + _ctx = self._select("asPatch", _args) + return File(_ctx) + + def before(self) -> "Directory": + """The older/lower snapshot to compare against.""" + _args: list[Arg] = [] + _ctx = self._select("before", _args) + return Directory(_ctx) + + async def diff_stats(self) -> list["DiffStat"]: + """Structured per-path diff statistics (kind and line counts) for this + changeset. + """ + _args: list[Arg] = [] + _ctx = self._select("diffStats", _args) + return await _ctx.execute_object_list(DiffStat) + + async def export(self, path: str) -> str: + """Applies the diff represented by this changeset to a path on the host. + + Parameters + ---------- + path: + Location of the copied directory (e.g., "logs/"). + + 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), + ] + _ctx = self._select("export", _args) + return await _ctx.execute(str) + + async def id(self) -> str: + """A unique identifier for this Changeset. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def is_empty(self) -> bool: + """Returns true if the changeset is empty (i.e. there are no changes). + + 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("isEmpty", _args) + return await _ctx.execute(bool) + + def layer(self) -> "Directory": + """Return a snapshot containing only the created and modified files""" + _args: list[Arg] = [] + _ctx = self._select("layer", _args) + return Directory(_ctx) + + async def modified_paths(self) -> list[str]: + """Files and directories that existed before and were updated in the + newer directory. + + 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("modifiedPaths", _args) + return await _ctx.execute(list[str]) + + async def removed_paths(self) -> list[str]: + """Files and directories that were removed. Directories are indicated by + a trailing slash, and their child paths are not included. + + 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("removedPaths", _args) + return await _ctx.execute(list[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 with_changeset(self, changes: Self, *, on_conflict: ChangesetMergeConflict | None = ChangesetMergeConflict.FAIL,) -> Self: + """Add changes to an existing changeset + + By default the operation will fail in case of conflicts, for instance + a file modified in both changesets. The behavior can be adjusted using + onConflict argument + + Parameters + ---------- + changes: + Changes to merge into the actual changeset + on_conflict: + What to do on a merge conflict + """ + _args = [ + Arg("changes", changes), + Arg("onConflict", on_conflict, ChangesetMergeConflict.FAIL), + ] + _ctx = self._select("withChangeset", _args) + return Changeset(_ctx) + + def with_changesets(self, changes: list["Changeset"], *, on_conflict: ChangesetsMergeConflict | None = ChangesetsMergeConflict.FAIL,) -> Self: + """Add changes from multiple changesets using git octopus merge strategy + + This is more efficient than chaining multiple withChangeset calls when + merging many changesets. + + Only FAIL and FAIL_EARLY conflict strategies are supported (octopus + merge cannot use -X ours/theirs). + + Parameters + ---------- + changes: + List of changesets to merge into the actual changeset + on_conflict: + What to do on a merge conflict + """ + _args = [ + Arg("changes", changes), + Arg("onConflict", on_conflict, ChangesetsMergeConflict.FAIL), + ] + _ctx = self._select("withChangesets", _args) + return Changeset(_ctx) + + def with_(self, cb: Callable[["Changeset"], "Changeset"]) -> "Changeset": + """Call the provided callable with current Changeset. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +@typecheck +class Check(Type): + + async def check_type(self) -> str: + """The type of check: 'check' for annotated checks, 'generate' for + generate-as-checks + + 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("checkType", _args) + return await _ctx.execute(str) + + async def completed(self) -> bool: + """Whether the check completed + + 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("completed", _args) + return await _ctx.execute(bool) + + async def description(self) -> str: + """The description of the check + + 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) + + def error(self) -> "Error": + """If the check failed, this is the error""" + _args: list[Arg] = [] + _ctx = self._select("error", _args) + return Error(_ctx) + + async def id(self) -> str: + """A unique identifier for this Check. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def name(self) -> str: + """Return the fully qualified name of the check + + 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 original_module(self) -> "Module": + """The original module in which the check has been defined""" + _args: list[Arg] = [] + _ctx = self._select("originalModule", _args) + return Module(_ctx) + + async def passed(self) -> bool: + """Whether the check passed + + 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("passed", _args) + return await _ctx.execute(bool) + + async def path(self) -> list[str]: + """The path of the check within its module + + 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("path", _args) + return await _ctx.execute(list[str]) + + async def result_emoji(self) -> str: + """An emoji representing the result of the check + + 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("resultEmoji", _args) + return await _ctx.execute(str) + + def run(self) -> Self: + """Execute the check""" + _args: list[Arg] = [] + _ctx = self._select("run", _args) + return Check(_ctx) + + def with_(self, cb: Callable[["Check"], "Check"]) -> "Check": + """Call the provided callable with current Check. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +@typecheck +class CheckGroup(Type): + + async def id(self) -> str: + """A unique identifier for this CheckGroup. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def list_(self) -> list[Check]: + """Return a list of individual checks and their details""" + _args: list[Arg] = [] + _ctx = self._select("list", _args) + return await _ctx.execute_object_list(Check) + + def report(self) -> "File": + """Generate a markdown report""" + _args: list[Arg] = [] + _ctx = self._select("report", _args) + return File(_ctx) + + def run(self, *, fail_fast: bool | None = None) -> Self: + """Execute all selected checks + + Parameters + ---------- + fail_fast: + If true, stop running checks as soon as any check fails. + """ + _args = [ + Arg("failFast", fail_fast, None), + ] + _ctx = self._select("run", _args) + return CheckGroup(_ctx) + + def with_(self, cb: Callable[["CheckGroup"], "CheckGroup"]) -> "CheckGroup": + """Call the provided callable with current CheckGroup. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +@typecheck +class ClientFilesyncMirror(Type): + """An internal persistent filesync mirror.""" + + async def id(self) -> str: + """A unique identifier for this ClientFilesyncMirror. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + +@typecheck +class Cloud(Type): + """Dagger Cloud configuration and state""" + + async def id(self) -> str: + """A unique identifier for this Cloud. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) + + async def combined_output(self) -> str: + """The combined buffered standard output and 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("combinedOutput", _args) + return await _ctx.execute(str) + + 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) + + def docker_healthcheck(self) -> "HealthcheckConfig": + """Retrieves this container's configured docker healthcheck.""" + _args: list[Arg] = [] + _ctx = self._select("dockerHealthcheck", _args) + return HealthcheckConfig(_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 persistent 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 persistent environment variables configured on + the container. + """ + _args: list[Arg] = [] + _ctx = self._select("envVariables", _args) + return await _ctx.execute_object_list(EnvVariable) + + async def exists(self, path: str, *, expected_type: ExistsType | None = None, do_not_follow_symlinks: bool | None = False, expand: bool | None = False,) -> bool: + """check if a file or directory exists + + Parameters + ---------- + path: + Path to check (e.g., "/file.txt"). + expected_type: + If specified, also validate the type of file (e.g. "REGULAR_TYPE", + "DIRECTORY_TYPE", or "SYMLINK_TYPE"). + do_not_follow_symlinks: + If specified, do not follow symlinks. + 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 + ------- + 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 = [ + Arg("path", path), + Arg("expectedType", expected_type, None), + Arg("doNotFollowSymlinks", do_not_follow_symlinks, False), + Arg("expand", expand, False), + ] + _ctx = self._select("exists", _args) + return await _ctx.execute(bool) + + 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 export_image(self, name: str, *, platform_variants: "list[Container] | None" = None, forced_compression: ImageLayerCompression | None = None, media_types: ImageMediaTypes | None = ImageMediaTypes.OCIMediaTypes,) -> Void: + """Exports the container as an image to the host's container image store. + + Parameters + ---------- + name: + Name of image to export to in the host's store + 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. + + Returns + ------- + Void + 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("name", name), + Arg("platformVariants", [] if platform_variants is None else platform_variants, []), + Arg("forcedCompression", forced_compression, None), + Arg("mediaTypes", media_types, ImageMediaTypes.OCIMediaTypes), + ] + _ctx = self._select("exportImage", _args) + await _ctx.execute() + + 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, *, registry_service: "Service | None" = None, protocol: RegistryProtocol | None = None, insecure_skip_tls_verify: bool | None = False,) -> 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" + registry_service: + Service to use as the registry endpoint for the image address. + The service will be started only for this pull. + protocol: + Protocol to use for registry communication. + Defaults to "HTTPS". Use "HTTP" only for plain HTTP registries. + insecure_skip_tls_verify: + Allow HTTPS registry communication without verifying the server + certificate. + """ + _args = [ + Arg("address", address), + Arg("registryService", registry_service, None), + Arg("protocol", protocol, None), + Arg("insecureSkipTLSVerify", insecure_skip_tls_verify, False), + ] + _ctx = self._select("from", _args) + return Container(_ctx) + + async def id(self) -> str: + """A unique identifier for this Container. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) + + def layer(self, id: str, *, forced_compression: ImageLayerCompression | None = None, media_types: ImageMediaTypes | None = ImageMediaTypes.OCIMediaTypes,) -> "File": + """Returns the image layer or configuration blob with the given digest as + a File. + + Parameters + ---------- + id: + Digest of the layer or configuration blob (e.g. + "sha256:abc123..."). + 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: + Media types to use for image layers. Defaults to OCI. + """ + _args = [ + Arg("id", id), + Arg("forcedCompression", forced_compression, None), + Arg("mediaTypes", media_types, ImageMediaTypes.OCIMediaTypes), + ] + _ctx = self._select("layer", _args) + return File(_ctx) + + def manifest(self, *, forced_compression: ImageLayerCompression | None = None, media_types: ImageMediaTypes | None = ImageMediaTypes.OCIMediaTypes,) -> "File": + """Computes and returns the manifest for this container as a File. + + Parameters + ---------- + 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: + Media types to use for image layers. Defaults to OCI. + """ + _args = [ + Arg("forcedCompression", forced_compression, None), + Arg("mediaTypes", media_types, ImageMediaTypes.OCIMediaTypes), + ] + _ctx = self._select("manifest", _args) + return File(_ctx) + + 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, registry_service: "Service | None" = None, protocol: RegistryProtocol | None = None, insecure_skip_tls_verify: bool | None = False,) -> 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. + registry_service: + Service to use as the registry endpoint for the image address. + The service will be started only for this push. + protocol: + Protocol to use for registry communication. + Defaults to "HTTPS". Use "HTTP" only for plain HTTP registries. + insecure_skip_tls_verify: + Allow HTTPS registry communication without verifying the server + certificate. + + 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), + Arg("registryService", registry_service, None), + Arg("protocol", protocol, None), + Arg("insecureSkipTLSVerify", insecure_skip_tls_verify, False), + ] + _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) + + def stat(self, path: str, *, do_not_follow_symlinks: bool | None = False,) -> "Stat": + """Return file status + + Parameters + ---------- + path: + Path to check (e.g., "/file.txt"). + do_not_follow_symlinks: + If specified, do not follow symlinks. + """ + _args = [ + Arg("path", path), + Arg("doNotFollowSymlinks", do_not_follow_symlinks, False), + ] + _ctx = self._select("stat", _args) + return Stat(_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 annotation. + + 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, source: "Directory", *, exclude: list[str] | None = None, include: list[str] | None = None, gitignore: bool | None = False, owner: str | None = '', inherit_owner: bool | None = False, expand: bool | None = False, permissions: int | None = None,) -> Self: + """Return a new container snapshot, with a directory added to its + filesystem + + Parameters + ---------- + path: + Location of the written directory (e.g., "/tmp/directory"). + source: + 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"]). + gitignore: + Apply .gitignore rules when writing the directory. + 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. + inherit_owner: + Set the owner to the container's current 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"). + permissions: + """ + _args = [ + Arg("path", path), + Arg("source", source), + Arg("exclude", [] if exclude is None else exclude, []), + Arg("include", [] if include is None else include, []), + Arg("gitignore", gitignore, False), + Arg("owner", owner, ''), + Arg("inheritOwner", inherit_owner, False), + Arg("expand", expand, False), + Arg("permissions", permissions, None), + ] + _ctx = self._select("withDirectory", _args) + return Container(_ctx) + + def with_docker_healthcheck(self, args: list[str], *, shell: bool | None = None, interval: str | None = None, timeout: str | None = None, start_period: str | None = None, start_interval: str | None = None, retries: int | None = None,) -> Self: + """Retrieves this container with the specificed docker healtcheck command + set. + + Parameters + ---------- + args: + Healthcheck command to execute. Example: ["go", "run", "main.go"]. + shell: + When true, command must be a single element, which is run using + the container's shell + interval: + Interval between running healthcheck. Example: "30s" + timeout: + Healthcheck timeout. Example: "3s" + start_period: + StartPeriod allows for failures during this initial startup period + which do not count towards maximum number of retries. Example: + "0s" + start_interval: + StartInterval configures the duration between checks during the + startup phase. Example: "5s" + retries: + The maximum number of consecutive failures before the container is + marked as unhealthy. Example: "3" + """ + _args = [ + Arg("args", args), + Arg("shell", shell, None), + Arg("interval", interval, None), + Arg("timeout", timeout, None), + Arg("startPeriod", start_period, None), + Arg("startInterval", start_interval, None), + Arg("retries", retries, None), + ] + _ctx = self._select("withDockerHealthcheck", _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_file_variables(self, source: "EnvFile") -> Self: + """Export environment variables from an env-file to the container. + + Parameters + ---------- + source: + Identifier of the envfile + """ + _args = [ + Arg("source", source), + ] + _ctx = self._select("withEnvFileVariables", _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_error(self, err: str) -> Self: + """Raise an error. + + Parameters + ---------- + err: + Message of the error to raise. If empty, the error will be + ignored. + """ + _args = [ + Arg("err", err), + ] + _ctx = self._select("withError", _args) + return Container(_ctx) + + def with_exec(self, args: list[str], *, use_entrypoint: bool | None = False, stdin: str | None = '', redirect_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_stdin: + Redirect the command's standard input from a file in the + container. Example: "./stdin.txt" + redirect_stdout: + Redirect the command's standard output to a file in the container. + Example: "./stdout.txt" + redirect_stderr: + Redirect the command's standard error to a file in the container. + Example: "./stderr.txt" + 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("redirectStdin", redirect_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 = '', inherit_owner: bool | None = False, 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. + inherit_owner: + Set the owner to the container's current 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("inheritOwner", inherit_owner, False), + 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 = '', inherit_owner: bool | None = False, 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. + inherit_owner: + Set the owner to the container's current 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("inheritOwner", inherit_owner, False), + 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 = '', inherit_owner: bool | None = False, 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. + inherit_owner: + Set the owner to the container's current 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("inheritOwner", inherit_owner, False), + Arg("expand", expand, False), + ] + _ctx = self._select("withMountedCache", _args) + return Container(_ctx) + + def with_mounted_directory(self, path: str, source: "Directory", *, owner: str | None = '', inherit_owner: bool | None = False, read_only: bool | None = False, 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. + inherit_owner: + Set the owner to the container's current user. + read_only: + Mount the directory read-only. + 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("inheritOwner", inherit_owner, False), + Arg("readOnly", read_only, False), + Arg("expand", expand, False), + ] + _ctx = self._select("withMountedDirectory", _args) + return Container(_ctx) + + def with_mounted_file(self, path: str, source: "File", *, owner: str | None = '', inherit_owner: bool | None = False, 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. + inherit_owner: + Set the owner to the container's current 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("inheritOwner", inherit_owner, False), + Arg("expand", expand, False), + ] + _ctx = self._select("withMountedFile", _args) + return Container(_ctx) + + def with_mounted_secret(self, path: str, source: "Secret", *, owner: str | None = '', inherit_owner: bool | None = False, 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. + inherit_owner: + Set the owner to the container's current 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("inheritOwner", inherit_owner, False), + 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_mounted_volume(self, path: str, volume: "Volume", *, read_only: bool | None = False, expand: bool | None = False,) -> Self: + """Retrieves this container plus a volume mounted at the given path. + + Parameters + ---------- + path: + Location of the volume mount (e.g., "/mnt/volume"). + volume: + Identifier of the volume to mount. + read_only: + Mount the volume read-only. + 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("volume", volume), + Arg("readOnly", read_only, False), + Arg("expand", expand, False), + ] + _ctx = self._select("withMountedVolume", _args) + return Container(_ctx) + + def with_new_file(self, path: str, contents: str, *, permissions: int | None = 420, owner: str | None = '', inherit_owner: bool | None = False, 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. + inherit_owner: + Set the owner to the container's current 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("inheritOwner", inherit_owner, False), + 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 = '', inherit_owner: bool | None = False, 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. + inherit_owner: + Set the owner to the container's current 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("inheritOwner", inherit_owner, False), + 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_volatile_variable(self, name: str, value: str) -> Self: + """Set a new non-secret environment variable for future execs without + invalidating exec cache when only its value changes. + + This is an expert-only escape hatch. If a volatile value affects + observable exec results, stale cached results may be reused. + + Parameters + ---------- + name: + Name of the volatile variable (e.g., "CI_RUN_ID"). + value: + Value of the volatile variable. + """ + _args = [ + Arg("name", name), + Arg("value", value), + ] + _ctx = self._select("withVolatileVariable", _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_docker_healthcheck(self) -> Self: + """Retrieves this container without a configured docker healtcheck + command. + """ + _args: list[Arg] = [] + _ctx = self._select("withoutDockerHealthcheck", _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_volatile_variable(self, name: str) -> Self: + """Retrieves this container minus the given volatile environment + variable. + + Parameters + ---------- + name: + The name of the volatile environment variable (e.g., "CI_RUN_ID"). + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("withoutVolatileVariable", _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.""" + + def as_sdk(self, workspace: "Workspace") -> "CurrentModuleAsSDK": + """Treat the currently executing module as an SDK installed in the given + workspace, exposing the modules and clients it manages. + + Errors if the current module is not installed as an SDK in this + workspace. + + Parameters + ---------- + workspace: + The workspace to resolve SDK-role data against. + """ + _args = [ + Arg("workspace", workspace), + ] + _ctx = self._select("asSDK", _args) + return CurrentModuleAsSDK(_ctx) + + 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) + + 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) + + def generators(self, *, include: list[str] | None = None,) -> "GeneratorGroup": + """Return all generators defined by the module + + .. caution:: + Experimental: This API is highly experimental and may be removed + or replaced entirely. + + Parameters + ---------- + include: + Only include generators matching the specified patterns + """ + _args = [ + Arg("include", include, None), + ] + _ctx = self._select("generators", _args) + return GeneratorGroup(_ctx) + + async def id(self) -> str: + """A unique identifier for this CurrentModule. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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, gitignore: bool | None = False,) -> "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.*"]). + gitignore: + Apply .gitignore filter rules inside the directory + """ + _args = [ + Arg("path", path), + Arg("exclude", [] if exclude is None else exclude, []), + Arg("include", [] if include is None else include, []), + Arg("gitignore", gitignore, False), + ] + _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 CurrentModuleAsSDK(Type): + """The SDK-role data for the currently executing module, as installed + in the supplied workspace.""" + + async def clients(self) -> list["CurrentModuleAsSDKClient"]: + """The generated clients this SDK produces in the workspace.""" + _args: list[Arg] = [] + _ctx = self._select("clients", _args) + return await _ctx.execute_object_list(CurrentModuleAsSDKClient) + + async def id(self) -> str: + """A unique identifier for this CurrentModuleAsSDK. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def modules(self) -> list["CurrentModuleAsSDKModule"]: + """The managed modules relevant to the bound workspace cwd: every module + at or below it, plus the nearest enclosing module when the cwd itself + is not managed. + """ + _args: list[Arg] = [] + _ctx = self._select("modules", _args) + return await _ctx.execute_object_list(CurrentModuleAsSDKModule) + + async def name(self) -> str: + """The user-facing name of this SDK in the workspace. + + 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 CurrentModuleAsSDKClient(Type): + """A generated client the current SDK produces in the workspace.""" + + async def id(self) -> str: + """A unique identifier for this CurrentModuleAsSDKClient. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def module(self) -> str: + """The module the client is bound to (workspace-relative path or + canonical 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("module", _args) + return await _ctx.execute(str) + + def module_source(self) -> "ModuleSource": + """The resolved module source this client is bound to, including its + dependency closure and pinned version. + """ + _args: list[Arg] = [] + _ctx = self._select("moduleSource", _args) + return ModuleSource(_ctx) + + async def path(self) -> str: + """Workspace-root-relative path of the generated client. + + 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("path", _args) + return await _ctx.execute(str) + + async def pin(self) -> str: + """The pinned version of the bound 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("pin", _args) + return await _ctx.execute(str) + + +@typecheck +class CurrentModuleAsSDKModule(Type): + """A workspace-local module managed by the current SDK.""" + + async def id(self) -> str: + """A unique identifier for this CurrentModuleAsSDKModule. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def path(self) -> str: + """Workspace-root-relative path to the managed 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("path", _args) + return await _ctx.execute(str) + + +@typecheck +class DiffStat(Type): + + async def added_lines(self) -> int: + """Number of added lines for this path. + + 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("addedLines", _args) + return await _ctx.execute(int) + + async def id(self) -> str: + """A unique identifier for this DiffStat. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def kind(self) -> DiffStatKind: + """Type of change. + + Returns + ------- + DiffStatKind + The type of change for a diff stat entry. + + 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(DiffStatKind) + + async def old_path(self) -> str | None: + """Previous path of the file, set only for renames. + + 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("oldPath", _args) + return await _ctx.execute(str | None) + + async def path(self) -> str: + """Path of the changed file or 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("path", _args) + return await _ctx.execute(str) + + async def removed_lines(self) -> int: + """Number of removed lines for this path. + + 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("removedLines", _args) + return await _ctx.execute(int) + + +@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 as_workspace(self, *, cwd: str | None = '/') -> "Workspace": + """Creates a synthetic workspace from this directory. + + Parameters + ---------- + cwd: + Current working directory inside the workspace root. Defaults to + the workspace root. + """ + _args = [ + Arg("cwd", cwd, '/'), + ] + _ctx = self._select("asWorkspace", _args) + return Workspace(_ctx) + + def changes(self, from_: Self) -> Changeset: + """Return the difference between this directory and another directory, + typically an older snapshot. + + The difference is encoded as a changeset, which also tracks removed + files, and can be applied to other directories. + + Parameters + ---------- + from_: + The base directory snapshot to compare against + """ + _args = [ + Arg("from", from_), + ] + _ctx = self._select("changes", _args) + return Changeset(_ctx) + + def chown(self, path: str, owner: str) -> Self: + """Change the owner of the directory contents recursively. + + Parameters + ---------- + path: + Path of the directory to change ownership of (e.g., "/"). + 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. + """ + _args = [ + Arg("path", path), + Arg("owner", owner), + ] + _ctx = self._select("chown", _args) + return Directory(_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, ssh: "Socket | None" = None,) -> 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. + ssh: + A socket to use for SSH authentication during the build + (e.g., for Dockerfile RUN --mount=type=ssh instructions). + Typically obtained via host.unixSocket() pointing to the + SSH_AUTH_SOCK. + """ + _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), + Arg("ssh", ssh, None), + ] + _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 exists(self, path: str, *, expected_type: ExistsType | None = None, do_not_follow_symlinks: bool | None = False,) -> bool: + """check if a file or directory exists + + Parameters + ---------- + path: + Path to check (e.g., "/file.txt"). + expected_type: + If specified, also validate the type of file (e.g. "REGULAR_TYPE", + "DIRECTORY_TYPE", or "SYMLINK_TYPE"). + do_not_follow_symlinks: + If specified, do not follow symlinks. + + 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 = [ + Arg("path", path), + Arg("expectedType", expected_type, None), + Arg("doNotFollowSymlinks", do_not_follow_symlinks, False), + ] + _ctx = self._select("exists", _args) + return await _ctx.execute(bool) + + 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, gitignore: bool | None = False,) -> 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.*"]). + gitignore: + If set, apply .gitignore rules when filtering the directory. + """ + _args = [ + Arg("exclude", [] if exclude is None else exclude, []), + Arg("include", [] if include is None else include, []), + Arg("gitignore", gitignore, False), + ] + _ctx = self._select("filter", _args) + return Directory(_ctx) + + async def find_up(self, name: str, start: str) -> str | None: + """Search up the directory tree for a file or directory, and return its + path. If no match, return null + + Parameters + ---------- + name: + The name of the file or directory to search for + start: + The path to start the search from + + 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), + Arg("start", start), + ] + _ctx = self._select("findUp", _args) + return await _ctx.execute(str | None) + + 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) -> str: + """A unique identifier for this Directory. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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 search(self, pattern: str, *, paths: list[str] | None = None, globs: list[str] | None = None, literal: bool | None = False, multiline: bool | None = False, dotall: bool | None = False, insensitive: bool | None = False, skip_ignored: bool | None = False, skip_hidden: bool | None = False, files_only: bool | None = False, limit: int | None = None,) -> list["SearchResult"]: + """Searches for content matching the given regular expression or literal + string. + + Uses Rust regex syntax; escape literal ., [, ], {, }, | with + backslashes. + + Parameters + ---------- + pattern: + The text to match. + paths: + Directory or file paths to search + globs: + Glob patterns to match (e.g., "*.md") + literal: + Interpret the pattern as a literal string instead of a regular + expression. + multiline: + Enable searching across multiple lines. + dotall: + Allow the . pattern to match newlines in multiline mode. + insensitive: + Enable case-insensitive matching. + skip_ignored: + Honor .gitignore, .ignore, and .rgignore files. + skip_hidden: + Skip hidden files (files starting with .). + files_only: + Only return matching files, not lines and content + limit: + Limit the number of results to return + """ + _args = [ + Arg("pattern", pattern), + Arg("paths", [] if paths is None else paths, []), + Arg("globs", [] if globs is None else globs, []), + Arg("literal", literal, False), + Arg("multiline", multiline, False), + Arg("dotall", dotall, False), + Arg("insensitive", insensitive, False), + Arg("skipIgnored", skip_ignored, False), + Arg("skipHidden", skip_hidden, False), + Arg("filesOnly", files_only, False), + Arg("limit", limit, None), + ] + _ctx = self._select("search", _args) + return await _ctx.execute_object_list(SearchResult) + + def stat(self, path: str, *, do_not_follow_symlinks: bool | None = False,) -> "Stat": + """Return file status + + Parameters + ---------- + path: + Path to stat (e.g., "/file.txt"). + do_not_follow_symlinks: + If specified, do not follow symlinks. + """ + _args = [ + Arg("path", path), + Arg("doNotFollowSymlinks", do_not_follow_symlinks, False), + ] + _ctx = self._select("stat", _args) + return Stat(_ctx) + + 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_changes(self, changes: Changeset) -> Self: + """Return a directory with changes from another directory applied to it. + + Parameters + ---------- + changes: + Changes to apply to the directory + """ + _args = [ + Arg("changes", changes), + ] + _ctx = self._select("withChanges", _args) + return Directory(_ctx) + + def with_directory(self, path: str, source: Self, *, exclude: list[str] | None = None, include: list[str] | None = None, gitignore: bool | None = False, owner: str | None = '', permissions: int | None = None,) -> Self: + """Return a snapshot with a directory added + + Parameters + ---------- + path: + Location of the written directory (e.g., "/src/"). + source: + 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.*"]). + gitignore: + Apply .gitignore filter rules inside the directory + owner: + A user:group to set for the copied 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. + permissions: + Permission given to the copied directory and contents (e.g., + 0755). + """ + _args = [ + Arg("path", path), + Arg("source", source), + Arg("exclude", [] if exclude is None else exclude, []), + Arg("include", [] if include is None else include, []), + Arg("gitignore", gitignore, False), + Arg("owner", owner, ''), + Arg("permissions", permissions, None), + ] + _ctx = self._select("withDirectory", _args) + return Directory(_ctx) + + def with_error(self, err: str) -> Self: + """Raise an error. + + Parameters + ---------- + err: + Message of the error to raise. If empty, the error will be + ignored. + """ + _args = [ + Arg("err", err), + ] + _ctx = self._select("withError", _args) + return Directory(_ctx) + + def with_file(self, path: str, source: "File", *, permissions: int | None = None, owner: str | 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). + owner: + A user:group to set for the copied 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. + """ + _args = [ + Arg("path", path), + Arg("source", source), + Arg("permissions", permissions, None), + Arg("owner", owner, ''), + ] + _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_patch(self, patch: str, *, on_conflict: PatchConflict | None = PatchConflict.FAIL,) -> Self: + """Retrieves this directory with the given Git-compatible patch applied. + + .. caution:: + Experimental: This API is highly experimental and may be removed + or replaced entirely. + + Parameters + ---------- + patch: + Patch to apply (e.g., "diff --git a/file.txt b/file.txt\nindex + 1234567..abcdef8 100644\n--- a/file.txt\n+++ b/file.txt\n@@ -1,1 + +1,1 @@\n-Hello\n+World\n"). + on_conflict: + How to handle hunks that no longer apply to the target content: + fail (default), or apply what fits and leave git-style conflict + markers where it doesn't. + """ + _args = [ + Arg("patch", patch), + Arg("onConflict", on_conflict, PatchConflict.FAIL), + ] + _ctx = self._select("withPatch", _args) + return Directory(_ctx) + + def with_patch_file(self, patch: "File", *, on_conflict: PatchConflict | None = PatchConflict.FAIL,) -> Self: + """Retrieves this directory with the given Git-compatible patch file + applied. + + .. caution:: + Experimental: This API is highly experimental and may be removed + or replaced entirely. + + Parameters + ---------- + patch: + File containing the patch to apply + on_conflict: + How to handle hunks that no longer apply to the target content: + fail (default), or apply what fits and leave git-style conflict + markers where it doesn't. + """ + _args = [ + Arg("patch", patch), + Arg("onConflict", on_conflict, PatchConflict.FAIL), + ] + _ctx = self._select("withPatchFile", _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) -> str: + """A unique identifier for this EnumTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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"]: + """The members of the enum. + + .. 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 deprecated(self) -> str | None: + """The reason this enum member is deprecated, if any. + + 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("deprecated", _args) + return await _ctx.execute(str | None) + + 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) -> str: + """A unique identifier for this EnumValueTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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 EnvFile(Type): + """A collection of environment variables.""" + + def as_file(self) -> "File": + """Return as a file""" + _args: list[Arg] = [] + _ctx = self._select("asFile", _args) + return File(_ctx) + + async def exists(self, name: str) -> bool: + """Check if a variable exists + + Parameters + ---------- + name: + Variable name + + 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 = [ + Arg("name", name), + ] + _ctx = self._select("exists", _args) + return await _ctx.execute(bool) + + async def get(self, name: str, *, raw: bool | None = None,) -> str: + """Lookup a variable (last occurrence wins) and return its value, or an + empty string + + Parameters + ---------- + name: + Variable name + raw: + Return the value exactly as written to the file. No quote removal + or variable expansion + + 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("name", name), + Arg("raw", raw, None), + ] + _ctx = self._select("get", _args) + return await _ctx.execute(str) + + async def id(self) -> str: + """A unique identifier for this EnvFile. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + def namespace(self, prefix: str) -> Self: + """Filters variables by prefix and removes the pref from keys. Variables + without the prefix are excluded. For example, with the prefix + "MY_APP_" and variables: MY_APP_TOKEN=topsecret MY_APP_NAME=hello + FOO=bar the resulting environment will contain: TOKEN=topsecret + NAME=hello + + Parameters + ---------- + prefix: + The prefix to filter by + """ + _args = [ + Arg("prefix", prefix), + ] + _ctx = self._select("namespace", _args) + return EnvFile(_ctx) + + async def variables(self, *, raw: bool | None = None) -> list["EnvVariable"]: + """Return all variables + + Parameters + ---------- + raw: + Return values exactly as written to the file. No quote removal or + variable expansion + """ + _args = [ + Arg("raw", raw, None), + ] + _ctx = self._select("variables", _args) + return await _ctx.execute_object_list(EnvVariable) + + def with_variable(self, name: str, value: str) -> Self: + """Add a variable + + Parameters + ---------- + name: + Variable name + value: + Variable value + """ + _args = [ + Arg("name", name), + Arg("value", value), + ] + _ctx = self._select("withVariable", _args) + return EnvFile(_ctx) + + def without_variable(self, name: str) -> Self: + """Remove all occurrences of the named variable + + Parameters + ---------- + name: + Variable name + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("withoutVariable", _args) + return EnvFile(_ctx) + + def with_(self, cb: Callable[["EnvFile"], "EnvFile"]) -> "EnvFile": + """Call the provided callable with current EnvFile. + + 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) -> str: + """A unique identifier for this EnvVariable. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) -> str: + """A unique identifier for this Error. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) -> str: + """A unique identifier for this ErrorValue. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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 deprecated(self) -> str | None: + """The reason this enum member is deprecated, if any. + + 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("deprecated", _args) + return await _ctx.execute(str | None) + + 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) -> str: + """A unique identifier for this FieldTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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.""" + + def as_env_file(self, *, expand: bool | None = None) -> EnvFile: + """Parse as an env file + + Parameters + ---------- + expand: + Replace "${VAR}" or "$VAR" with the value of other vars + .. deprecated:: Variable expansion is now enabled by default + """ + _args = [ + Arg("expand", expand, None), + ] + _ctx = self._select("asEnvFile", _args) + return EnvFile(_ctx) + + def as_json(self) -> "JSONValue": + """Parse the file contents as JSON.""" + _args: list[Arg] = [] + _ctx = self._select("asJSON", _args) + return JSONValue(_ctx) + + def chown(self, owner: str) -> Self: + """Change the owner of the file recursively. + + Parameters + ---------- + 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. + """ + _args = [ + Arg("owner", owner), + ] + _ctx = self._select("chown", _args) + return File(_ctx) + + async def contents(self, *, offset_lines: int | None = None, limit_lines: int | None = None,) -> str: + """Retrieves the contents of the file. + + Parameters + ---------- + offset_lines: + Start reading after this line + limit_lines: + Maximum number of lines to read + + 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("offsetLines", offset_lines, None), + Arg("limitLines", limit_lines, None), + ] + _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) -> str: + """A unique identifier for this File. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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 search(self, pattern: str, *, literal: bool | None = False, multiline: bool | None = False, dotall: bool | None = False, insensitive: bool | None = False, skip_ignored: bool | None = False, skip_hidden: bool | None = False, files_only: bool | None = False, limit: int | None = None, paths: list[str] | None = None, globs: list[str] | None = None,) -> list["SearchResult"]: + """Searches for content matching the given regular expression or literal + string. + + Uses Rust regex syntax; escape literal ., [, ], {, }, | with + backslashes. + + Parameters + ---------- + pattern: + The text to match. + literal: + Interpret the pattern as a literal string instead of a regular + expression. + multiline: + Enable searching across multiple lines. + dotall: + Allow the . pattern to match newlines in multiline mode. + insensitive: + Enable case-insensitive matching. + skip_ignored: + Honor .gitignore, .ignore, and .rgignore files. + skip_hidden: + Skip hidden files (files starting with .). + files_only: + Only return matching files, not lines and content + limit: + Limit the number of results to return + paths: + globs: + """ + _args = [ + Arg("pattern", pattern), + Arg("literal", literal, False), + Arg("multiline", multiline, False), + Arg("dotall", dotall, False), + Arg("insensitive", insensitive, False), + Arg("skipIgnored", skip_ignored, False), + Arg("skipHidden", skip_hidden, False), + Arg("filesOnly", files_only, False), + Arg("limit", limit, None), + Arg("paths", [] if paths is None else paths, []), + Arg("globs", [] if globs is None else globs, []), + ] + _ctx = self._select("search", _args) + return await _ctx.execute_object_list(SearchResult) + + 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) + + def stat(self) -> "Stat": + """Return file status""" + _args: list[Arg] = [] + _ctx = self._select("stat", _args) + return Stat(_ctx) + + 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_replaced(self, search: str, replacement: str, *, all: bool | None = False, first_from: int | None = None,) -> Self: + """Retrieves the file with content replaced with the given text. + + If 'all' is true, all occurrences of the pattern will be replaced. + + If 'firstAfter' is specified, only the first match starting at the + specified line will be replaced. + + If neither are specified, and there are multiple matches for the + pattern, this will error. + + If there are no matches for the pattern, this will error. + + Parameters + ---------- + search: + The text to match. + replacement: + The text to match. + all: + Replace all occurrences of the pattern. + first_from: + Replace the first match starting from the specified line. + """ + _args = [ + Arg("search", search), + Arg("replacement", replacement), + Arg("all", all, False), + Arg("firstFrom", first_from, None), + ] + _ctx = self._select("withReplaced", _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 deprecated(self) -> str | None: + """The reason this function is deprecated, if any. + + 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("deprecated", _args) + return await _ctx.execute(str | None) + + 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) -> str: + """A unique identifier for this Function. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) + + async def source_module_name(self) -> str: + """If this function is provided by 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) + + def with_agent(self) -> Self: + """Returns the function with a flag indicating it is an agent middleware.""" + _args: list[Arg] = [] + _ctx = self._select("withAgent", _args) + return Function(_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, deprecated: str | None = None, default_address: str | 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. + deprecated: + If deprecated, the reason or migration path. + default_address: + """ + _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), + Arg("deprecated", deprecated, None), + Arg("defaultAddress", default_address, ''), + ] + _ctx = self._select("withArg", _args) + return Function(_ctx) + + def with_cache_policy(self, policy: FunctionCachePolicy, *, time_to_live: str | None = None,) -> Self: + """Returns the function updated to use the provided cache policy. + + Parameters + ---------- + policy: + The cache policy to use. + time_to_live: + The TTL for the cache policy, if applicable. Provided as a + duration string, e.g. "5m", "1h30s". + """ + _args = [ + Arg("policy", policy), + Arg("timeToLive", time_to_live, None), + ] + _ctx = self._select("withCachePolicy", _args) + return Function(_ctx) + + def with_check(self) -> Self: + """Returns the function with a flag indicating it's a check.""" + _args: list[Arg] = [] + _ctx = self._select("withCheck", _args) + return Function(_ctx) + + def with_deprecated(self, *, reason: str | None = None) -> Self: + """Returns the function with the provided deprecation reason. + + Parameters + ---------- + reason: + Reason or migration path describing the deprecation. + """ + _args = [ + Arg("reason", reason, None), + ] + _ctx = self._select("withDeprecated", _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_generator(self) -> Self: + """Returns the function with a flag indicating it's a generator.""" + _args: list[Arg] = [] + _ctx = self._select("withGenerator", _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_up(self) -> Self: + """Returns the function with a flag indicating it returns a service for + dagger up. + """ + _args: list[Arg] = [] + _ctx = self._select("withUp", _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_address(self) -> str: + """Only applies to arguments of type Container. If the argument is not + set, load it from the given address (e.g. alpine:latest) + + 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("defaultAddress", _args) + return await _ctx.execute(str) + + 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 deprecated(self) -> str | None: + """The reason this function is deprecated, if any. + + 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("deprecated", _args) + return await _ctx.execute(str | None) + + 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) -> str: + """A unique identifier for this FunctionArg. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) -> str: + """A unique identifier for this FunctionCall. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) -> str: + """A unique identifier for this FunctionCallArgValue. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) -> str: + """A unique identifier for this GeneratedCode. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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 Generator(Type): + + def changes(self) -> Changeset: + """The generated changeset from the last run""" + _args: list[Arg] = [] + _ctx = self._select("changes", _args) + return Changeset(_ctx) + + async def completed(self) -> bool: + """Whether the generator complete + + 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("completed", _args) + return await _ctx.execute(bool) + + async def description(self) -> str: + """Return the description of the generator + + 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) -> str: + """A unique identifier for this Generator. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def is_empty(self) -> bool: + """Whether changeset from the last generator run is empty or not + + 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("isEmpty", _args) + return await _ctx.execute(bool) + + async def name(self) -> str: + """Return the fully qualified name of the generator + + 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 original_module(self) -> "Module": + """The original module in which the generator has been defined""" + _args: list[Arg] = [] + _ctx = self._select("originalModule", _args) + return Module(_ctx) + + async def path(self) -> list[str]: + """The path of the generator within its module + + 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("path", _args) + return await _ctx.execute(list[str]) + + def run(self) -> Self: + """Execute the generator""" + _args: list[Arg] = [] + _ctx = self._select("run", _args) + return Generator(_ctx) + + def with_(self, cb: Callable[["Generator"], "Generator"]) -> "Generator": + """Call the provided callable with current Generator. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +@typecheck +class GeneratorGroup(Type): + + def changes(self, *, on_conflict: ChangesetsMergeConflict | None = ChangesetsMergeConflict.FAIL_EARLY,) -> Changeset: + """The combined changes from the last run of the generators + + If any conflict occurs, for instance if the same file is modified by + multiple generators, or if a file is both modified and deleted, an + error is raised and the merge of the changesets will failed. + + Set 'continueOnConflicts' flag to force to merge the changes in a + 'last write wins' strategy. + + Parameters + ---------- + on_conflict: + Strategy to apply on conflicts between generators + """ + _args = [ + Arg("onConflict", on_conflict, ChangesetsMergeConflict.FAIL_EARLY), + ] + _ctx = self._select("changes", _args) + return Changeset(_ctx) + + async def id(self) -> str: + """A unique identifier for this GeneratorGroup. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def is_empty(self) -> bool: + """Whether the generated changeset from the last run is empty or not + + 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("isEmpty", _args) + return await _ctx.execute(bool) + + async def list_(self) -> list[Generator]: + """Return a list of individual generators and their details""" + _args: list[Arg] = [] + _ctx = self._select("list", _args) + return await _ctx.execute_object_list(Generator) + + async def load_failures(self) -> list[str]: + """Load failures tolerated while collecting the generators. + + Empty unless a workspace module could not be loaded during an unscoped + 'dagger generate' (no selector), where load failures are tolerated so + the modules that do load still generate. Each entry is a human- + readable error message. An explicit selector keeps failing hard + instead. + + 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("loadFailures", _args) + return await _ctx.execute(list[str]) + + def run(self) -> Self: + """Execute all selected generators""" + _args: list[Arg] = [] + _ctx = self._select("run", _args) + return GeneratorGroup(_ctx) + + def with_(self, cb: Callable[["GeneratorGroup"], "GeneratorGroup"]) -> "GeneratorGroup": + """Call the provided callable with current GeneratorGroup. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +@typecheck +class GitCommit(Type): + """An immutable git commit.""" + + def ancestor_release_tag(self, *, include_pre_release: bool | None = False,) -> "GitRef": + """The latest semver release tag reachable from this commit. + + Parameters + ---------- + include_pre_release: + Include pre-release tags when choosing the latest tag. + """ + _args = [ + Arg("includePreRelease", include_pre_release, False), + ] + _ctx = self._select("ancestorReleaseTag", _args) + return GitRef(_ctx) + + async def author_email(self) -> str: + """Git author email. + + 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("authorEmail", _args) + return await _ctx.execute(str) + + async def author_name(self) -> str: + """Git author 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("authorName", _args) + return await _ctx.execute(str) + + async def authored_date(self) -> str: + """Git author date, in RFC3339 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("authoredDate", _args) + return await _ctx.execute(str) + + async def committed_date(self) -> str: + """Git committer date, in RFC3339 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("committedDate", _args) + return await _ctx.execute(str) + + async def committer_email(self) -> str: + """Git committer email. + + 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("committerEmail", _args) + return await _ctx.execute(str) + + async def committer_name(self) -> str: + """Git committer 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("committerName", _args) + return await _ctx.execute(str) + + async def id(self) -> str: + """A unique identifier for this GitCommit. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def message(self) -> str: + """Full commit message. + + 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 message_body(self) -> str: + """Commit message body, excluding the headline. + + 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("messageBody", _args) + return await _ctx.execute(str) + + async def message_headline(self) -> str: + """First line of the commit message. + + 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("messageHeadline", _args) + return await _ctx.execute(str) + + async def parent_shas(self) -> list[str]: + """Parent commit SHAs. + + 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("parentShas", _args) + return await _ctx.execute(list[str]) + + def release_tag(self, *, include_pre_release: bool | None = False,) -> "GitRef": + """The latest semver release tag that points directly at this commit. + + Parameters + ---------- + include_pre_release: + Include pre-release tags when choosing the latest tag. + """ + _args = [ + Arg("includePreRelease", include_pre_release, False), + ] + _ctx = self._select("releaseTag", _args) + return GitRef(_ctx) + + async def sha(self) -> str: + """The full commit SHA. + + 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("sha", _args) + return await _ctx.execute(str) + + async def short_sha(self) -> str: + """The abbreviated commit SHA. + + 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("shortSha", _args) + return await _ctx.execute(str) + + def tree(self, *, discard_git_dir: bool | None = False, depth: int | None = 1, include_tags: bool | None = False,) -> Directory: + """The filesystem tree at this commit. + + Parameters + ---------- + discard_git_dir: + Set to true to discard .git directory. + depth: + The depth of the tree to fetch. + include_tags: + Set to true to populate tag refs in the local checkout .git. + """ + _args = [ + Arg("discardGitDir", discard_git_dir, False), + Arg("depth", depth, 1), + Arg("includeTags", include_tags, False), + ] + _ctx = self._select("tree", _args) + return Directory(_ctx) + + +@typecheck +class GitRef(Type): + """A git ref (tag, branch, or commit).""" + + def as_workspace(self, *, cwd: str | None = '/') -> "Workspace": + """Creates a synthetic workspace from this git ref. + + Parameters + ---------- + cwd: + Current working directory inside the workspace root. Defaults to + the workspace root. + """ + _args = [ + Arg("cwd", cwd, '/'), + ] + _ctx = self._select("asWorkspace", _args) + return Workspace(_ctx) + + async def commit(self) -> str: + """The resolved commit id at this ref. + + .. deprecated:: + Use "commitSHA" instead. + + 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. + """ + warnings.warn( + "Method \"commit\" is deprecated: Use \"commitSHA\" instead.", + DeprecationWarning, + stacklevel=4, + ) + _args: list[Arg] = [] + _ctx = self._select("commit", _args) + return await _ctx.execute(str) + + async def commit_sha(self) -> str: + """The resolved commit SHA 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("commitSHA", _args) + return await _ctx.execute(str) + + def common_ancestor(self, other: Self) -> Self: + """Find the best common ancestor between this ref and another ref. + + Parameters + ---------- + other: + The other ref to compare against. + """ + _args = [ + Arg("other", other), + ] + _ctx = self._select("commonAncestor", _args) + return GitRef(_ctx) + + async def id(self) -> str: + """A unique identifier for this GitRef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def log(self, *, limit: int | None = 10, paths: list[str] | None = None, base: "GitRef | None" = None,) -> list[GitCommit]: + """Commits reachable from this ref, newest first, starting with the + commit this ref resolves to. + + Parameters + ---------- + limit: + Maximum number of commits to return. + paths: + Only include commits touching these paths, relative to the root of + the repository. + base: + Exclude commits reachable from this ref, i.e. only list commits + added on top of it. + """ + _args = [ + Arg("limit", limit, 10), + Arg("paths", paths, None), + Arg("base", base, None), + ] + _ctx = self._select("log", _args) + return await _ctx.execute_object_list(GitCommit) + + async def name(self) -> str: + """The resolved name of 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("name", _args) + return await _ctx.execute(str) + + async def ref(self) -> str: + """The resolved ref name at this ref. + + .. deprecated:: + Use "name" instead. + + 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. + """ + warnings.warn( + "Method \"ref\" is deprecated: Use \"name\" instead.", + DeprecationWarning, + stacklevel=4, + ) + _args: list[Arg] = [] + _ctx = self._select("ref", _args) + return await _ctx.execute(str) + + def target_commit(self) -> GitCommit: + """The commit this ref resolves to.""" + _args: list[Arg] = [] + _ctx = self._select("targetCommit", _args) + return GitCommit(_ctx) + + def tree(self, *, discard_git_dir: bool | None = False, depth: int | None = 1, include_tags: bool | None = False,) -> 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. + include_tags: + Set to true to populate tag refs in the local checkout .git. + """ + _args = [ + Arg("discardGitDir", discard_git_dir, False), + Arg("depth", depth, 1), + Arg("includeTags", include_tags, False), + ] + _ctx = self._select("tree", _args) + return Directory(_ctx) + + def with_(self, cb: Callable[["GitRef"], "GitRef"]) -> "GitRef": + """Call the provided callable with current GitRef. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +@typecheck +class GitRepository(Type): + """A git repository.""" + + def as_workspace(self, *, cwd: str | None = '/') -> "Workspace": + """Creates a synthetic workspace from this git repository. + + Parameters + ---------- + cwd: + Current working directory inside the workspace root. Defaults to + the workspace root. + """ + _args = [ + Arg("cwd", cwd, '/'), + ] + _ctx = self._select("asWorkspace", _args) + return Workspace(_ctx) + + 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) -> GitCommit: + """Returns details of a commit. + + Parameters + ---------- + id: + Identifier of the commit (e.g., + "b6315d8f2810962c601af73f86831f6866ea798b"). + """ + _args = [ + Arg("id", id), + ] + _ctx = self._select("commit", _args) + return GitCommit(_ctx) + + def head(self) -> GitRef: + """Returns details for HEAD.""" + _args: list[Arg] = [] + _ctx = self._select("head", _args) + return GitRef(_ctx) + + async def id(self) -> str: + """A unique identifier for this GitRepository. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + def latest_version(self) -> GitRef: + """Returns details for the latest semver tag.""" + _args: list[Arg] = [] + _ctx = self._select("latestVersion", _args) + return GitRef(_ctx) + + 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 uncommitted(self) -> Changeset: + """Returns the changeset of uncommitted changes in the git repository.""" + _args: list[Arg] = [] + _ctx = self._select("uncommitted", _args) + return Changeset(_ctx) + + async def url(self) -> str | None: + """The URL of the git repository. + + 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("url", _args) + return await _ctx.execute(str | None) + + +@typecheck +class HTTPState(Type): + """An internal persistent HTTP state.""" + + async def id(self) -> str: + """A unique identifier for this HTTPState. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + +@typecheck +class HealthcheckConfig(Type): + """Image healthcheck configuration.""" + + async def args(self) -> list[str]: + """Healthcheck command 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("args", _args) + return await _ctx.execute(list[str]) + + async def id(self) -> str: + """A unique identifier for this HealthcheckConfig. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def interval(self) -> str: + """Interval between running healthcheck. Example:30s + + 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("interval", _args) + return await _ctx.execute(str) + + async def retries(self) -> int: + """The maximum number of consecutive failures before the container is + marked as unhealthy. Example:3 + + 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("retries", _args) + return await _ctx.execute(int) + + async def shell(self) -> bool: + """Healthcheck command is a shell command. + + 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("shell", _args) + return await _ctx.execute(bool) + + async def start_interval(self) -> str: + """StartInterval configures the duration between checks during the + startup phase. Example:5s + + 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("startInterval", _args) + return await _ctx.execute(str) + + async def start_period(self) -> str: + """StartPeriod allows for failures during this initial startup period + which do not count towards maximum number of retries. Example:0s + + 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("startPeriod", _args) + return await _ctx.execute(str) + + async def timeout(self) -> str: + """Healthcheck timeout. Example:3s + + 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("timeout", _args) + return await _ctx.execute(str) + + +@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) -> str: + """A unique identifier for this InputTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) -> str: + """A unique identifier for this InterfaceTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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 JSONValue(Type): + + async def as_array(self) -> list["JSONValue"]: + """Decode an array from json""" + _args: list[Arg] = [] + _ctx = self._select("asArray", _args) + return await _ctx.execute_object_list(JSONValue) + + async def as_boolean(self) -> bool: + """Decode a boolean from json + + 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("asBoolean", _args) + return await _ctx.execute(bool) + + async def as_integer(self) -> int: + """Decode an integer from json + + 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("asInteger", _args) + return await _ctx.execute(int) + + async def as_string(self) -> str: + """Decode a string from 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("asString", _args) + return await _ctx.execute(str) + + async def contents(self, *, pretty: bool | None = False, indent: str | None = ' ',) -> JSON: + """Return the value encoded as json + + Parameters + ---------- + pretty: + Pretty-print + indent: + Optional line prefix + + 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 = [ + Arg("pretty", pretty, False), + Arg("indent", indent, ' '), + ] + _ctx = self._select("contents", _args) + return await _ctx.execute(JSON) + + def field(self, path: list[str]) -> Self: + """Lookup the field at the given path, and return its value. + + Parameters + ---------- + path: + Path of the field to lookup, encoded as an array of field names + """ + _args = [ + Arg("path", path), + ] + _ctx = self._select("field", _args) + return JSONValue(_ctx) + + async def fields(self) -> list[str]: + """List fields of the encoded object + + 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("fields", _args) + return await _ctx.execute(list[str]) + + async def id(self) -> str: + """A unique identifier for this JSONValue. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + def new_boolean(self, value: bool) -> Self: + """Encode a boolean to json + + Parameters + ---------- + value: + New boolean value + """ + _args = [ + Arg("value", value), + ] + _ctx = self._select("newBoolean", _args) + return JSONValue(_ctx) + + def new_integer(self, value: int) -> Self: + """Encode an integer to json + + Parameters + ---------- + value: + New integer value + """ + _args = [ + Arg("value", value), + ] + _ctx = self._select("newInteger", _args) + return JSONValue(_ctx) + + def new_string(self, value: str) -> Self: + """Encode a string to json + + Parameters + ---------- + value: + New string value + """ + _args = [ + Arg("value", value), + ] + _ctx = self._select("newString", _args) + return JSONValue(_ctx) + + def with_contents(self, contents: JSON) -> Self: + """Return a new json value, decoded from the given content + + Parameters + ---------- + contents: + New JSON-encoded contents + """ + _args = [ + Arg("contents", contents), + ] + _ctx = self._select("withContents", _args) + return JSONValue(_ctx) + + def with_field(self, path: list[str], value: Self) -> Self: + """Set a new field at the given path + + Parameters + ---------- + path: + Path of the field to set, encoded as an array of field names + value: + The new value of the field + """ + _args = [ + Arg("path", path), + Arg("value", value), + ] + _ctx = self._select("withField", _args) + return JSONValue(_ctx) + + def with_(self, cb: Callable[["JSONValue"], "JSONValue"]) -> "JSONValue": + """Call the provided callable with current JSONValue. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +@typecheck +class LLM(Type): + """A conversation with a large language model (LLM): queue prompts, + expose tools, and step the model until it completes its turn.""" + + async def context_tokens(self) -> int: + """estimated number of tokens currently occupying the context window; + unlike tokenUsage this is not cumulative over the session + + 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("contextTokens", _args) + return await _ctx.execute(int) + + async def context_window(self) -> int | None: + """The model's total context window in tokens, or null if unknown (e.g. a + local or uncatalogued model). + + Returns + ------- + int | None + 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("contextWindow", _args) + return await _ctx.execute(int | None) + + def fork(self, label: str) -> Self: + """Fork the conversation, so that otherwise-identical follow-ups evaluate + independently instead of deduplicating to a single cached result. + + Parameters + ---------- + label: + A label distinguishing this fork from its siblings, e.g. + "attempt-2" when retrying a flaky evaluation. + """ + _args = [ + Arg("label", label), + ] + _ctx = self._select("fork", _args) + return LLM(_ctx) + + async def has_pending(self) -> bool: + """Report whether anything is queued to send to the model: an unsent + prompt or unevaluated tool results. When true, another step will do + work; when false, the turn is complete. + + 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("hasPending", _args) + return await _ctx.execute(bool) + + async def id(self) -> str: + """A unique identifier for this LLM. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def last_reply(self) -> str: + """The text of the model's most recent reply. + + 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, *, max_steps: int | None = None, max_tokens: int | None = None,) -> Self: + """Send the queued prompt and step the model against the available tools, + until it ends its turn: a reply with no tool calls and nothing left + queued. + + Parameters + ---------- + max_steps: + Cap the number of steps. The loop fails if the cap is reached + before the model ends its turn. + max_tokens: + Cap the model's output tokens on each step. Defaults to the + model's maximum. + """ + _args = [ + Arg("maxSteps", max_steps, None), + Arg("maxTokens", max_tokens, None), + ] + _ctx = self._select("loop", _args) + return LLM(_ctx) + + async def messages(self) -> list["LLMMessage"]: + """The full message history, as structured messages.""" + _args: list[Arg] = [] + _ctx = self._select("messages", _args) + return await _ctx.execute_object_list(LLMMessage) + + async def model(self) -> str: + """The model the conversation is running against, after resolving any + configured default. + + 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 portable_id(self) -> str: + """A portable, self-contained ID for the conversation that node() can + resolve in any session. Unlike id, which may return an engine-local + runtime handle valid only within the current session, this returns the + recipe form suitable for persisting and later restoring the + conversation. The recipe is flattened: bindings superseded during the + session (workspace overlays recorded by each mutating tool call, and + re-bound toolsets) are dropped, while the current workspace binding — + including any pending, un-exported edits — is preserved. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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("portableID", _args) + return await _ctx.execute(str) + + async def provider(self) -> str: + """The provider serving the model, e.g. "anthropic", "openai", "google", + or "local". + + 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 reasoning_effort(self) -> str: + """The reasoning effort in use, e.g. "low", "medium", or "high". Empty or + "none" when reasoning is disabled. + + 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("reasoningEffort", _args) + return await _ctx.execute(str) + + async def replay(self) -> Self: + """Re-emit telemetry spans for the full message history, so a loaded + conversation displays in the TUI. + + 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, "replay", _args) + + async def skills(self) -> list["LLMSkill"]: + """The skills visible to the model, exactly as the ListSkills tool serves + them: engine-embedded skills, skills installed with withSkills, and + skills discovered in the workspace. + """ + _args: list[Arg] = [] + _ctx = self._select("skills", _args) + return await _ctx.execute_object_list(LLMSkill) + + def step(self, *, max_tokens: int | None = None) -> Self: + """Advance the conversation by a single step: send the queued prompt or + tool results to the model, evaluate any tool calls it makes, and queue + their results. Use loop to step until the model ends its turn. + + Parameters + ---------- + max_tokens: + Cap the model's output tokens for this step. Defaults to the + model's maximum. + """ + _args = [ + Arg("maxTokens", max_tokens, None), + ] + _ctx = self._select("step", _args) + return LLM(_ctx) + + async def sync(self) -> Self: + """Force evaluation of the conversation's pending operations (prompts, + steps, loops) 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 token_usage(self) -> "LLMTokenUsage": + """The cumulative token usage, summed across every API call in the + conversation. + """ + _args: list[Arg] = [] + _ctx = self._select("tokenUsage", _args) + return LLMTokenUsage(_ctx) + + async def tools(self) -> str: + """Render documentation for the tools currently exposed to the model. + + 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) + + async def transcript(self) -> str: + """The message history rendered as a plain-text transcript, suitable for + feeding back to an LLM (e.g. for summarization). + + 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("transcript", _args) + return await _ctx.execute(str) + + def with_mcp_server(self, name: str, service: "Service") -> Self: + """Add an external MCP server to the LLM + + Parameters + ---------- + name: + The name of the MCP server + service: + The MCP service to run and communicate with over stdio + """ + _args = [ + Arg("name", name), + Arg("service", service), + ] + _ctx = self._select("withMCPServer", _args) + return LLM(_ctx) + + def with_model(self, model: str, *, provider: str | None = None,) -> Self: + """Change the model for the rest of the conversation. The message history + is preserved; the new model takes effect on the next step. + + Parameters + ---------- + model: + The model to use, e.g. "claude-sonnet-4-5" or "gpt-5.4". + provider: + The provider serving the model, e.g. "openai". Overrides the + provider otherwise inferred from the model name — useful when the + name matches no known pattern (e.g. a fine-tune), or matches the + wrong one. + """ + _args = [ + Arg("model", model), + Arg("provider", provider, None), + ] + _ctx = self._select("withModel", _args) + return LLM(_ctx) + + def with_prompt(self, prompt: str) -> Self: + """Queue a user prompt, to be sent to the model on the next step or loop. + + 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: + """Queue a file's contents as a user prompt, like withPrompt. + + Parameters + ---------- + file: + The file to read the prompt from + """ + _args = [ + Arg("file", file), + ] + _ctx = self._select("withPromptFile", _args) + return LLM(_ctx) + + def with_reasoning_effort(self, effort: str) -> Self: + """Change the reasoning effort for the rest of the conversation, + overriding any configured default. The message history is preserved; + the new effort takes effect on the next step. + + Parameters + ---------- + effort: + The reasoning effort, e.g. "low", "medium", or "high"; "none" + disables reasoning. Supported levels are model-specific — some + models also accept e.g. "minimal", "xhigh", or "max". + """ + _args = [ + Arg("effort", effort), + ] + _ctx = self._select("withReasoningEffort", _args) + return LLM(_ctx) + + def with_response(self, content: list[LLMContentBlockInput], *, input_tokens: int | None = 0, output_tokens: int | None = 0, cached_token_reads: int | None = 0, cached_token_writes: int | None = 0, total_tokens: int | None = 0,) -> Self: + """Append an assistant response to the message history without calling + the model, e.g. to reconstruct a conversation from another source. + + Parameters + ---------- + content: + The response content + input_tokens: + Uncached input tokens sent + output_tokens: + Tokens received from the model, including text and tool calls + cached_token_reads: + Cached input tokens read + cached_token_writes: + Cached input tokens written + total_tokens: + Total tokens consumed by this response + """ + _args = [ + Arg("content", content), + Arg("inputTokens", input_tokens, 0), + Arg("outputTokens", output_tokens, 0), + Arg("cachedTokenReads", cached_token_reads, 0), + Arg("cachedTokenWrites", cached_token_writes, 0), + Arg("totalTokens", total_tokens, 0), + ] + _ctx = self._select("withResponse", _args) + return LLM(_ctx) + + def with_skills(self, directory: Directory) -> Self: + """Install skills from a directory, adding them to the skills the model + discovers with ListSkills and reads with ReadSkill. Each skill is a + directory containing a SKILL.md with name and description frontmatter, + discovered anywhere in the tree. Installed skills take precedence over + skills discovered in the workspace, but cannot shadow the engine's + built-in skills. + + Parameters + ---------- + directory: + A directory containing skills, each a subdirectory holding a + SKILL.md. + """ + _args = [ + Arg("directory", directory), + ] + _ctx = self._select("withSkills", _args) + return LLM(_ctx) + + def with_system_prompt(self, prompt: str) -> Self: + """Add a system prompt, instructing the model across the whole + conversation. + + Parameters + ---------- + prompt: + The system prompt to send + """ + _args = [ + Arg("prompt", prompt), + ] + _ctx = self._select("withSystemPrompt", _args) + return LLM(_ctx) + + def with_tool_result(self, call_id: str, content: str, errored: bool,) -> Self: + """Append the result of a tool call to the message history. + + Parameters + ---------- + call_id: + The ID of the tool call this result responds to + content: + The content returned by the tool + errored: + Whether the tool call resulted in an error + """ + _args = [ + Arg("callId", call_id), + Arg("content", content), + Arg("errored", errored), + ] + _ctx = self._select("withToolResult", _args) + return LLM(_ctx) + + def with_tools(self, object: Node, *, except_: list[str] | None = None,) -> Self: + """Expose an object's methods as tools. Every eligible method of the + bound object becomes a tool; a tool that returns this object's own + type replaces it as the new state. Repeatable to bind several objects. + + Parameters + ---------- + object: + The object whose methods become tools. + except_: + Method names to exclude from the toolset (e.g. constructors, + entrypoints). + """ + _args = [ + Arg("object", object), + Arg("except", [] if except_ is None else except_, []), + ] + _ctx = self._select("withTools", _args) + return LLM(_ctx) + + def with_workspace(self, workspace: "Workspace") -> Self: + """Bind the LLM to a workspace, exposing its modules as tools exactly as + the Dagger CLI would serve them for that workspace. + + Parameters + ---------- + workspace: + The workspace to work in. + """ + _args = [ + Arg("workspace", workspace), + ] + _ctx = self._select("withWorkspace", _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 without_message_history(self) -> Self: + """Clear the message history, keeping only the system prompts.""" + _args: list[Arg] = [] + _ctx = self._select("withoutMessageHistory", _args) + return LLM(_ctx) + + def without_system_prompts(self) -> Self: + """Clear the user-added system prompts, keeping only the default system + prompt. + """ + _args: list[Arg] = [] + _ctx = self._select("withoutSystemPrompts", _args) + return LLM(_ctx) + + def workspace(self) -> "Workspace": + """Return the workspace the LLM is bound to.""" + _args: list[Arg] = [] + _ctx = self._select("workspace", _args) + return Workspace(_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 LLMContentBlock(Type): + """A single piece of content within an LLM message.""" + + async def arguments(self) -> JSON: + """The arguments passed to the tool, JSON-encoded (for TOOL_CALL kind). + + 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("arguments", _args) + return await _ctx.execute(JSON) + + async def call_id(self) -> str: + """The unique ID of a tool call (for TOOL_CALL or TOOL_RESULT kinds). + + 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("callId", _args) + return await _ctx.execute(str) + + async def errored(self) -> bool: + """Whether the tool call resulted in an error (for TOOL_RESULT kind). + + 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("errored", _args) + return await _ctx.execute(bool) + + async def id(self) -> str: + """A unique identifier for this LLMContentBlock. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def kind(self) -> LLMContentBlockKind: + """The kind of content block, which determines the other populated + fields. + + Returns + ------- + LLMContentBlockKind + The kind of content in a message block. + + 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(LLMContentBlockKind) + + async def signature(self) -> str: + """Provider-specific opaque data (e.g. Anthropic thinking signature). + Preserve it when reconstructing a conversation. + + 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("signature", _args) + return await _ctx.execute(str) + + async def text(self) -> str: + """Text content (for TEXT, THINKING, or TOOL_RESULT kinds). + + 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("text", _args) + return await _ctx.execute(str) + + async def tool_name(self) -> str: + """The name of the tool called (for TOOL_CALL kind). + + 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("toolName", _args) + return await _ctx.execute(str) + + +@typecheck +class LLMMessage(Type): + """A single message in an LLM conversation.""" + + async def content(self) -> list[LLMContentBlock]: + """The message's content blocks, in the order the model produced them.""" + _args: list[Arg] = [] + _ctx = self._select("content", _args) + return await _ctx.execute_object_list(LLMContentBlock) + + async def id(self) -> str: + """A unique identifier for this LLMMessage. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def role(self) -> LLMMessageRole: + """The role that produced this message. + + Returns + ------- + LLMMessageRole + The role that generated a message. + + 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("role", _args) + return await _ctx.execute(LLMMessageRole) + + def token_usage(self) -> "LLMTokenUsage": + """Token usage reported by the provider for the API call that produced + this message; all zeros except on assistant responses. + """ + _args: list[Arg] = [] + _ctx = self._select("tokenUsage", _args) + return LLMTokenUsage(_ctx) + + +@typecheck +class LLMSkill(Type): + """A skill available to a model: task-specific guidance discovered + with ListSkills and read with ReadSkill.""" + + async def description(self) -> str: + """The one-line description from the SKILL.md frontmatter. + + 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) -> str: + """A unique identifier for this LLMSkill. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def name(self) -> str: + """The skill name, as passed to ReadSkill. + + 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 LLMTokenUsage(Type): + """A count of tokens consumed by LLM API calls.""" + + async def cached_token_reads(self) -> int: + """Input tokens served from the provider's prompt cache. + + 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: + """Input tokens written to the provider's prompt cache. + + 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) -> str: + """A unique identifier for this LLMTokenUsage. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def input_tokens(self) -> int: + """Uncached input tokens sent to the model. + + 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: + """Tokens received from the model, including text and tool calls. + + 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: + """Total tokens consumed, as reported by the provider. + + 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) -> str: + """A unique identifier for this Label. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) -> str: + """A unique identifier for this ListTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + +@typecheck +class Module(Type): + """A Dagger module.""" + + def check(self, name: str) -> Check: + """Return the check defined by the module with the given name. Must match + to exactly one check. + + .. caution:: + Experimental: This API is highly experimental and may be removed + or replaced entirely. + + Parameters + ---------- + name: + The name of the check to retrieve + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("check", _args) + return Check(_ctx) + + def checks(self, *, include: list[str] | None = None, no_generate: bool | None = None,) -> CheckGroup: + """Return all checks defined by the module + + .. caution:: + Experimental: This API is highly experimental and may be removed + or replaced entirely. + + Parameters + ---------- + include: + Only include checks matching the specified patterns + no_generate: + When true, only return annotated check functions; exclude + generate-as-checks + """ + _args = [ + Arg("include", include, None), + Arg("noGenerate", no_generate, None), + ] + _ctx = self._select("checks", _args) + return CheckGroup(_ctx) + + 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) + + def generator(self, name: str) -> Generator: + """Return the generator defined by the module with the given name. Must + match to exactly one generator. + + .. caution:: + Experimental: This API is highly experimental and may be removed + or replaced entirely. + + Parameters + ---------- + name: + The name of the generator to retrieve + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("generator", _args) + return Generator(_ctx) + + def generators(self, *, include: list[str] | None = None,) -> GeneratorGroup: + """Return all generators defined by the module + + .. caution:: + Experimental: This API is highly experimental and may be removed + or replaced entirely. + + Parameters + ---------- + include: + Only include generators matching the specified patterns + """ + _args = [ + Arg("include", include, None), + ] + _ctx = self._select("generators", _args) + return GeneratorGroup(_ctx) + + async def id(self) -> str: + """A unique identifier for this Module. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) + + def introspection_schema_json(self) -> File: + """The introspection schema JSON file for this module. + + This file represents the schema visible to the module's source code, + including all core types and those from the dependencies. + + Note: this is in the context of a module, so some core types may be + hidden. + """ + _args: list[Arg] = [] + _ctx = self._select("introspectionSchemaJSON", _args) + return File(_ctx) + + 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, entrypoint: 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 + entrypoint: + Install the module as the entrypoint, promoting its main-object + methods onto the Query root + + 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), + Arg("entrypoint", entrypoint, None), + ] + _ctx = self._select("serve", _args) + await _ctx.execute() + + def services(self, *, include: list[str] | None = None,) -> "UpGroup": + """Return all services defined by the module + + .. caution:: + Experimental: This API is highly experimental and may be removed + or replaced entirely. + + Parameters + ---------- + include: + Only include services matching the specified patterns + """ + _args = [ + Arg("include", include, None), + ] + _ctx = self._select("services", _args) + return UpGroup(_ctx) + + 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 user_defaults(self) -> EnvFile: + """User-defined default values, loaded from local .env files.""" + _args: list[Arg] = [] + _ctx = self._select("userDefaults", _args) + return EnvFile(_ctx) + + 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) -> str: + """A unique identifier for this ModuleConfigClient. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + +@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) + + def blueprint(self) -> Self: + """The blueprint referenced by the module source. + + .. deprecated:: + Legacy dagger.json field. Generic module loading no longer honors + it; use workspace modules in dagger.toml instead. + """ + warnings.warn( + "Method \"blueprint\" is deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in dagger.toml instead.", + DeprecationWarning, + stacklevel=4, + ) + _args: list[Arg] = [] + _ctx = self._select("blueprint", _args) + return ModuleSource(_ctx) + + def client_schema_introspection_json(self) -> File: + """The client-facing introspection schema JSON file for this module + source. + + This is the schema consumed by client codegen: unlike + introspectionSchemaJSON (the module-facing schema), it hides no core + types and installs this module (reached via dag.) so a + generated client can bind it. The module's dependencies are excluded: + a client is generated for a single module plus core, not its + dependency graph. + """ + _args: list[Arg] = [] + _ctx = self._select("clientSchemaIntrospectionJSON", _args) + return File(_ctx) + + 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 module config file 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 generate(self, workspace: "Workspace") -> "Workspace": + """Return the supplied workspace with this module's generated context + applied. + + The workspace change baseline is preserved, so a later + Workspace.changes call includes this generation together with any + other edits made by the caller. + + Parameters + ---------- + workspace: + The workspace to apply generated files to. + """ + _args = [ + Arg("workspace", workspace), + ] + _ctx = self._select("generate", _args) + return Workspace(_ctx) + + def generate_local_dependencies(self, workspace: "Workspace") -> Changeset: + """Generate this module's transitive local dependency closure and return + the staged changes as a single changeset against the unstaged + workspace root. + + Each local dependency is generated by its own SDK against a workspace + scoped to it, carrying the dependency's own already-generated + dependencies. Remote (git) dependencies are assumed committed and + skipped. Overlay the result onto the workspace before generating this + module; it is not this module's own generated code. + + Parameters + ---------- + workspace: + The workspace to generate the local dependencies against. + """ + _args = [ + Arg("workspace", workspace), + ] + _ctx = self._select("generateLocalDependencies", _args) + return Changeset(_ctx) + + def generated_context_changeset(self) -> Changeset: + """The generated files and directories made on top of the module source's + context directory, returned as a Changeset. + """ + _args: list[Arg] = [] + _ctx = self._select("generatedContextChangeset", _args) + return Changeset(_ctx) + + 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) -> str: + """A unique identifier for this ModuleSource. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + def introspection_schema_json(self) -> File: + """The introspection schema JSON file for this module source. + + This file represents the schema visible to the module's source code, + including all core types and those from the dependencies. + + Note: this is in the context of a module, so some core types may be + hidden. + """ + _args: list[Arg] = [] + _ctx = self._select("introspectionSchemaJSON", _args) + return File(_ctx) + + 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 config file + (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 + config. + + 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 toolchains(self) -> list["ModuleSource"]: + """The toolchains referenced by the module source. + + .. deprecated:: + Legacy dagger.json field. Generic module loading no longer honors + it; use workspace modules in dagger.toml instead. + """ + warnings.warn( + "Method \"toolchains\" is deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in dagger.toml instead.", + DeprecationWarning, + stacklevel=4, + ) + _args: list[Arg] = [] + _ctx = self._select("toolchains", _args) + return await _ctx.execute_object_list(ModuleSource) + + def updated_config_directory(self) -> Directory: + """The module's dagger.json with any in-memory edits from with* APIs + applied, as a diff relative to the source's context directory. + + Unlike generatedContextDirectory, this does not run codegen and does + not validate the engine version against the running engine, so it can + be used to declare an engine requirement newer than the running + engine. Loading or serving such a module still fails at + moduleSource.asModule. + """ + _args: list[Arg] = [] + _ctx = self._select("updatedConfigDirectory", _args) + return Directory(_ctx) + + def user_defaults(self) -> EnvFile: + """User-defined defaults read from local .env files""" + _args: list[Arg] = [] + _ctx = self._select("userDefaults", _args) + return EnvFile(_ctx) + + 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_blueprint(self, blueprint: Self) -> Self: + """Set a blueprint for the module source. + + .. deprecated:: + Legacy dagger.json field. Generic module loading no longer honors + it; use workspace modules in `dagger.toml` instead. + + Parameters + ---------- + blueprint: + The blueprint module to set. + """ + warnings.warn( + "Method \"with_blueprint\" is deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in `dagger.toml` instead.", + DeprecationWarning, + stacklevel=4, + ) + _args = [ + Arg("blueprint", blueprint), + ] + _ctx = self._select("withBlueprint", _args) + return ModuleSource(_ctx) + + 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_experimental_features(self, features: list[ModuleSourceExperimentalFeature],) -> Self: + """Enable the experimental features for the module source. + + Parameters + ---------- + features: + The experimental features to enable. + """ + _args = [ + Arg("features", features), + ] + _ctx = self._select("withExperimentalFeatures", _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_toolchains(self, toolchains: list["ModuleSource"]) -> Self: + """Add toolchains to the module source. + + .. deprecated:: + Legacy dagger.json field. Generic module loading no longer honors + it; use workspace modules in `dagger.toml` instead. + + Parameters + ---------- + toolchains: + The toolchain modules to add. + """ + warnings.warn( + "Method \"with_toolchains\" is deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in `dagger.toml` instead.", + DeprecationWarning, + stacklevel=4, + ) + _args = [ + Arg("toolchains", toolchains), + ] + _ctx = self._select("withToolchains", _args) + return ModuleSource(_ctx) + + def with_update_blueprint(self) -> Self: + """Update the blueprint module to the latest version. + + .. deprecated:: + Legacy dagger.json field. Generic module loading no longer honors + it; use workspace modules in `dagger.toml` instead. + """ + warnings.warn( + "Method \"with_update_blueprint\" is deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in `dagger.toml` instead.", + DeprecationWarning, + stacklevel=4, + ) + _args: list[Arg] = [] + _ctx = self._select("withUpdateBlueprint", _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 with_update_toolchains(self, toolchains: list[str]) -> Self: + """Update one or more toolchains. + + .. deprecated:: + Legacy dagger.json field. Generic module loading no longer honors + it; use workspace modules in `dagger.toml` instead. + + Parameters + ---------- + toolchains: + The toolchains to update. + """ + warnings.warn( + "Method \"with_update_toolchains\" is deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in `dagger.toml` instead.", + DeprecationWarning, + stacklevel=4, + ) + _args = [ + Arg("toolchains", toolchains), + ] + _ctx = self._select("withUpdateToolchains", _args) + return ModuleSource(_ctx) + + def with_updated_clients(self, clients: list[str]) -> Self: + """Update one or more clients. + + Parameters + ---------- + clients: + The clients to update + """ + _args = [ + Arg("clients", clients), + ] + _ctx = self._select("withUpdatedClients", _args) + return ModuleSource(_ctx) + + def without_blueprint(self) -> Self: + """Remove the current blueprint from the module source. + + .. deprecated:: + Legacy dagger.json field. Generic module loading no longer honors + it; use workspace modules in `dagger.toml` instead. + """ + warnings.warn( + "Method \"without_blueprint\" is deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in `dagger.toml` instead.", + DeprecationWarning, + stacklevel=4, + ) + _args: list[Arg] = [] + _ctx = self._select("withoutBlueprint", _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 without_experimental_features(self, features: list[ModuleSourceExperimentalFeature],) -> Self: + """Disable experimental features for the module source. + + Parameters + ---------- + features: + The experimental features to disable. + """ + _args = [ + Arg("features", features), + ] + _ctx = self._select("withoutExperimentalFeatures", _args) + return ModuleSource(_ctx) + + def without_toolchains(self, toolchains: list[str]) -> Self: + """Remove the provided toolchains from the module source. + + .. deprecated:: + Legacy dagger.json field. Generic module loading no longer honors + it; use workspace modules in `dagger.toml` instead. + + Parameters + ---------- + toolchains: + The toolchains to remove. + """ + warnings.warn( + "Method \"without_toolchains\" is deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in `dagger.toml` instead.", + DeprecationWarning, + stacklevel=4, + ) + _args = [ + Arg("toolchains", toolchains), + ] + _ctx = self._select("withoutToolchains", _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 deprecated(self) -> str | None: + """The reason this enum member is deprecated, if any. + + 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("deprecated", _args) + return await _ctx.execute(str | None) + + 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) -> str: + """A unique identifier for this ObjectTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) -> str: + """A unique identifier for this Port. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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 Query(Root): + """The root of the DAG.""" + + def address(self, value: str) -> Address: + """initialize an address to load directories, containers, secrets or + other object types. + """ + _args = [ + Arg("value", value), + ] + _ctx = self._select("address", _args) + return Address(_ctx) + + def cache_volume(self, key: str, *, source: Directory | None = None, sharing: CacheSharingMode | None = CacheSharingMode.SHARED, owner: str | None = '',) -> CacheVolume: + """Constructs a cache volume for a given cache key. + + Parameters + ---------- + key: + A string identifier to target this cache volume (e.g., "modules- + cache"). + 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 cache volume root. + 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. + """ + _args = [ + Arg("key", key), + Arg("source", source, None), + Arg("sharing", sharing, CacheSharingMode.SHARED), + Arg("owner", owner, ''), + ] + _ctx = self._select("cacheVolume", _args) + return CacheVolume(_ctx) + + def changeset(self) -> Changeset: + """Creates an empty changeset""" + _args: list[Arg] = [] + _ctx = self._select("changeset", _args) + return Changeset(_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) + + def current_node(self) -> Node: + """The object that received the current module function call, as a Node. + Errors when there is no current call, or the call is top-level (e.g. a + module constructor). + """ + _args: list[Arg] = [] + _ctx = self._select("currentNode", _args) + return _NodeClient(_ctx) + + async def current_type_defs(self, *, return_all_types: bool | None = False, hide_core: bool | None = None,) -> list["TypeDef"]: + """The TypeDef representations of the objects currently being served in + the session. + + Parameters + ---------- + return_all_types: + Return the full referenced typedef closure instead of only top- + level served typedefs. + hide_core: + Strip core API functions from the Query type, leaving only module- + sourced functions (constructors, entrypoint proxies, etc.). + Core types (Container, Directory, etc.) are kept so return types + and method chaining still work. + """ + _args = [ + Arg("returnAllTypes", return_all_types, False), + Arg("hideCore", hide_core, None), + ] + _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_file(self, *, expand: bool | None = None) -> EnvFile: + """Initialize an environment file + + Parameters + ---------- + expand: + Replace "${VAR}" or "$VAR" with the value of other vars + .. deprecated:: Variable expansion is now enabled by default + """ + _args = [ + Arg("expand", expand, None), + ] + _ctx = self._select("envFile", _args) + return EnvFile(_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. + .. 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, checksum: str | 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. + checksum: + Expected digest of the downloaded content (e.g., "sha256:..."). + 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("checksum", checksum, None), + Arg("authHeader", auth_header, None), + Arg("experimentalServiceHost", experimental_service_host, None), + ] + _ctx = self._select("http", _args) + return File(_ctx) + + async def id(self) -> str: + """A unique identifier for this Query. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + def json(self) -> JSONValue: + """Initialize a JSON value""" + _args: list[Arg] = [] + _ctx = self._select("json", _args) + return JSONValue(_ctx) + + def llm(self, *, model: str | None = None, provider: str | None = None,) -> LLM: + """Initialize a new LLM conversation. + + .. caution:: + Experimental: LLM support is not yet stabilized + + Parameters + ---------- + model: + The model to converse with, e.g. "claude-sonnet-4-5" or "gpt-5.4". + Defaults to the configured default model. + provider: + The provider serving the model, e.g. "openai". Overrides the + provider otherwise inferred from the model name — useful when the + name matches no known pattern (e.g. a fine-tune), or matches the + wrong one. + """ + _args = [ + Arg("model", model, None), + Arg("provider", provider, None), + ] + _ctx = self._select("llm", _args) + return LLM(_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 a module config file 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 node(self, id: Type) -> Node: + """Load any object by its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("node", _args) + return _NodeClient(_ctx) + + def schema(self, json: JSON) -> "Schema": + """Load a GraphQL introspection schema for merging. + + Parameters + ---------- + json: + The introspection schema JSON to load. + """ + _args = [ + Arg("json", json), + ] + _ctx = self._select("schema", _args) + return Schema(_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 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 RemoteGitMirror(Type): + """An internal persistent bare git mirror.""" + + async def id(self) -> str: + """A unique identifier for this RemoteGitMirror. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + +@typecheck +class SDKConfig(Type): + """The SDK config of the module.""" + + async def debug(self) -> bool: + """Whether to start the SDK runtime in debug mode with an interactive + terminal. + + 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("debug", _args) + return await _ctx.execute(bool) + + async def id(self) -> str: + """A unique identifier for this SDKConfig. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) -> str: + """A unique identifier for this ScalarTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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 Schema(Type): + """A GraphQL introspection schema that can be inspected and merged.""" + + async def contents(self) -> JSON: + """Serialize the schema back to introspection 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("contents", _args) + return await _ctx.execute(JSON) + + async def id(self) -> str: + """A unique identifier for this Schema. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + def merge(self, module_types: JSON, module_name: str,) -> Self: + """Merge a module's introspection-shaped type definitions into the + schema, returning the combined schema. + + Parameters + ---------- + module_types: + Introspection JSON describing the types the module defines. + Object, interface and enum types are appended to the schema, and a + constructor field for the module is added to the Query type. + module_name: + The name of the module whose types are being merged. Used to stamp + the @sourceMap directive and to derive the module's constructor + field. + """ + _args = [ + Arg("moduleTypes", module_types), + Arg("moduleName", module_name), + ] + _ctx = self._select("merge", _args) + return Schema(_ctx) + + def with_(self, cb: Callable[["Schema"], "Schema"]) -> "Schema": + """Call the provided callable with current Schema. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +@typecheck +class SearchResult(Type): + + async def absolute_offset(self) -> int: + """The byte offset of this line within the file. + + 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("absoluteOffset", _args) + return await _ctx.execute(int) + + async def file_path(self) -> str: + """The path to the file that matched. + + 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("filePath", _args) + return await _ctx.execute(str) + + async def id(self) -> str: + """A unique identifier for this SearchResult. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def line_number(self) -> int: + """The first line that matched. + + 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("lineNumber", _args) + return await _ctx.execute(int) + + async def matched_lines(self) -> str: + """The line content that matched. + + 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("matchedLines", _args) + return await _ctx.execute(str) + + async def submatches(self) -> list["SearchSubmatch"]: + """Sub-match positions and content within the matched lines.""" + _args: list[Arg] = [] + _ctx = self._select("submatches", _args) + return await _ctx.execute_object_list(SearchSubmatch) + + +@typecheck +class SearchSubmatch(Type): + + async def end(self) -> int: + """The match's end offset within the matched lines. + + 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("end", _args) + return await _ctx.execute(int) + + async def id(self) -> str: + """A unique identifier for this SearchSubmatch. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def start(self) -> int: + """The match's start offset within the matched lines. + + 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("start", _args) + return await _ctx.execute(int) + + async def text(self) -> str: + """The matched text. + + 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("text", _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) -> str: + """A unique identifier for this Secret. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) -> str: + """A unique identifier for this Service. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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 sync(self) -> Self: + """Forces evaluation of the pipeline 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, *, cmd: list[str] | None = None) -> Self: + _args = [ + Arg("cmd", [] if cmd is None else cmd, []), + ] + _ctx = self._select("terminal", _args) + return Service(_ctx) + + 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) -> str: + """A unique identifier for this Socket. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + +@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) -> str: + """A unique identifier for this SourceMap. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) + + async def url(self) -> str: + """The URL to the file, if any. This can be used to link to the source + map in the browser. + + 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("url", _args) + return await _ctx.execute(str) + + +@typecheck +class Stat(Type): + """A file or directory status object.""" + + async def file_type(self) -> FileType | None: + """file type + + Returns + ------- + FileType | None + File type. + + 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("fileType", _args) + return await _ctx.execute(FileType | None) + + async def id(self) -> str: + """A unique identifier for this Stat. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def name(self) -> str: + """file 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 permissions(self) -> int: + """permission bits + + 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("permissions", _args) + return await _ctx.execute(int) + + async def size(self) -> int: + """file size + + 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) + + +@typecheck +class Terminal(Type): + """An interactive terminal that clients can connect to.""" + + async def id(self) -> str: + """A unique identifier for this Terminal. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(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__() + + +@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) -> str: + """A unique identifier for this TypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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 name(self) -> str: + """The canonical non-optional name of the 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("name", _args) + return await _ctx.execute(str) + + 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, deprecated: str | 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. + deprecated: + If deprecated, the reason or migration path. + """ + _args = [ + Arg("name", name), + Arg("value", value, ''), + Arg("description", description, ''), + Arg("sourceMap", source_map, None), + Arg("deprecated", deprecated, None), + ] + _ctx = self._select("withEnumMember", _args) + return TypeDef(_ctx) + + def with_enum_value(self, value: str, *, description: str | None = '', source_map: SourceMap | None = None, deprecated: str | 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. + deprecated: + If deprecated, the reason or migration path. + """ + 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), + Arg("deprecated", deprecated, 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, deprecated: str | 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. + deprecated: + If deprecated, the reason or migration path. + """ + _args = [ + Arg("name", name), + Arg("typeDef", type_def), + Arg("description", description, ''), + Arg("sourceMap", source_map, None), + Arg("deprecated", deprecated, 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, deprecated: str | 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), + Arg("deprecated", deprecated, 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) + + + +@typecheck +class Up(Type): + + async def description(self) -> str: + """The description of the service + + 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) -> str: + """A unique identifier for this Up. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def name(self) -> str: + """Return the fully qualified name of the service + + 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 original_module(self) -> Module: + """The original module in which the service has been defined""" + _args: list[Arg] = [] + _ctx = self._select("originalModule", _args) + return Module(_ctx) + + async def path(self) -> list[str]: + """The path of the service within its module + + 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("path", _args) + return await _ctx.execute(list[str]) + + def run(self) -> Self: + """Execute the service function""" + _args: list[Arg] = [] + _ctx = self._select("run", _args) + return Up(_ctx) + + def with_(self, cb: Callable[["Up"], "Up"]) -> "Up": + """Call the provided callable with current Up. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +@typecheck +class UpGroup(Type): + + async def id(self) -> str: + """A unique identifier for this UpGroup. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def list_(self) -> list[Up]: + """Return a list of individual services and their details""" + _args: list[Arg] = [] + _ctx = self._select("list", _args) + return await _ctx.execute_object_list(Up) + + def run(self) -> Self: + """Execute all selected service functions""" + _args: list[Arg] = [] + _ctx = self._select("run", _args) + return UpGroup(_ctx) + + def with_(self, cb: Callable[["UpGroup"], "UpGroup"]) -> "UpGroup": + """Call the provided callable with current UpGroup. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +@typecheck +class Volume(Type): + """A filesystem volume that can be mounted into containers.""" + + async def id(self) -> str: + """A unique identifier for this Volume. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + +@typecheck +class Workspace(Type): + """A Dagger workspace detected from the current working directory or + constructed from a Directory.""" + + async def address(self) -> str: + """Canonical Dagger address of the workspace location, or an opaque + identity for synthetic workspaces. + + 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("address", _args) + return await _ctx.execute(str) + + def agents(self, *, include: list[str] | None = None,) -> AgentGroup: + """Return all agent middlewares from modules loaded in the workspace. + + Parameters + ---------- + include: + Only include agents matching the specified patterns + """ + _args = [ + Arg("include", include, None), + ] + _ctx = self._select("agents", _args) + return AgentGroup(_ctx) + + def changes(self, *, from_: "Workspace | None" = None) -> Changeset: + """Return this workspace's changes, with paths relative to its working + directory. + + Pass from to compare against an earlier workspace state. Omitting it + preserves the cumulative behavior used by clients from before this + argument was added. + + Parameters + ---------- + from_: + An earlier workspace state to compare against. + """ + _args = [ + Arg("from", from_, None), + ] + _ctx = self._select("changes", _args) + return Changeset(_ctx) + + def checks(self, *, include: list[str] | None = None, skip: list[str] | None = None, no_generate: bool | None = None, only_generate: bool | None = None,) -> CheckGroup: + """Return all checks from modules loaded in the workspace. + + Parameters + ---------- + include: + Only include checks matching the specified patterns + skip: + Skip checks matching the specified patterns + no_generate: + When true, only return annotated check functions; exclude + generate-as-checks + only_generate: + When true, only return generate-as-checks; exclude annotated check + functions + """ + _args = [ + Arg("include", include, None), + Arg("skip", skip, None), + Arg("noGenerate", no_generate, None), + Arg("onlyGenerate", only_generate, None), + ] + _ctx = self._select("checks", _args) + return CheckGroup(_ctx) + + async def config_file(self) -> str: + """Selected native workspace config file relative to the workspace cwd, + 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("configFile", _args) + return await _ctx.execute(str) + + async def config_read(self, *, key: str | None = '') -> str: + """Read a configuration value from dagger.toml. + + If key is empty, returns the full config. + + If key points to a scalar, returns the value. + + If key points to a table, returns flattened dotted-key output. + + Parameters + ---------- + key: + Dotted key path (e.g. modules.greeter.source). Empty for full + config. + + 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("key", key, ''), + ] + _ctx = self._select("configRead", _args) + return await _ctx.execute(str) + + async def cwd(self) -> str: + """Current location within the workspace root. + + The workspace root is returned as "/". + + Relative paths in workspace APIs resolve from here. + + 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("cwd", _args) + return await _ctx.execute(str) + + def directory(self, path: str, *, exclude: list[str] | None = None, include: list[str] | None = None, gitignore: bool | None = False,) -> Directory: + """Returns a Directory from the workspace. + + Relative paths resolve from the workspace cwd. Absolute paths resolve + from the workspace root. + + Parameters + ---------- + path: + Location of the directory to retrieve. Relative paths (e.g., + "src") resolve from the workspace cwd; absolute paths (e.g., + "/src") resolve from the workspace root. + 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.*"]). + gitignore: + Apply .gitignore filter rules inside the directory. + """ + _args = [ + Arg("path", path), + Arg("exclude", [] if exclude is None else exclude, []), + Arg("include", [] if include is None else include, []), + Arg("gitignore", gitignore, False), + ] + _ctx = self._select("directory", _args) + return Directory(_ctx) + + async def env_list(self) -> list[str]: + """List named environments defined in the workspace configuration. + + 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("envList", _args) + return await _ctx.execute(list[str]) + + async def export(self) -> Void: + """Write this workspace's pending changes to its local Git workspace. + + Returns + ------- + Void + 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: list[Arg] = [] + _ctx = self._select("export", _args) + await _ctx.execute() + + def file(self, path: str) -> File: + """Returns a File from the workspace. + + Relative paths resolve from the workspace cwd. Absolute paths resolve + from the workspace root. + + Parameters + ---------- + path: + Location of the file to retrieve. Relative paths (e.g., "go.mod") + resolve from the workspace cwd; absolute paths (e.g., "/go.mod") + resolve from the workspace root. + """ + _args = [ + Arg("path", path), + ] + _ctx = self._select("file", _args) + return File(_ctx) + + async def find_roots(self, markers: list[str], *, start: str | None = '.', exclude: list[str] | None = None,) -> list[str]: + """Find project roots marked by any of the given filenames, starting from + a path relative to the workspace cwd. + + Returns cwd-relative directory paths for every marked directory at or + below start, plus the nearest marked ancestor when start itself is not + marked. + + Each returned path is usable as-is with other workspace APIs, e.g. + directory(path). + + Parameters + ---------- + markers: + File basenames that mark a project root (e.g. ["go.mod"] or + ["deno.json", "deno.jsonc"]). + start: + Directory to start from. Relative paths resolve from the workspace + cwd. + exclude: + Glob patterns pruning the walk below start (e.g. + ["**/node_modules/**"]). + + 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("markers", markers), + Arg("start", start, '.'), + Arg("exclude", [] if exclude is None else exclude, []), + ] + _ctx = self._select("findRoots", _args) + return await _ctx.execute(list[str]) + + async def find_up(self, name: str, *, from_: str | None = '.',) -> str | None: + """Search for a file or directory by walking up from the start path + within the workspace. + + Returns the absolute workspace path if found, or null if not found. + + Relative start paths resolve from the workspace cwd. + + The search stops at the workspace root and will not traverse above it. + + Parameters + ---------- + name: + The name of the file or directory to search for. + from_: + Path to start the search from. Relative paths resolve from the + workspace cwd; absolute paths resolve from the workspace root. + + 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), + Arg("from", from_, '.'), + ] + _ctx = self._select("findUp", _args) + return await _ctx.execute(str | None) + + def generators(self, *, include: list[str] | None = None,) -> GeneratorGroup: + """Return all generators from modules loaded in the workspace. + + Parameters + ---------- + include: + Only include generators matching the specified patterns + """ + _args = [ + Arg("include", include, None), + ] + _ctx = self._select("generators", _args) + return GeneratorGroup(_ctx) + + def git(self) -> "WorkspaceGit": + """Git state for this workspace. Errors if the workspace is not in a git + repository. + """ + _args: list[Arg] = [] + _ctx = self._select("git", _args) + return WorkspaceGit(_ctx) + + async def glob(self, pattern: str) -> list[str]: + """Returns a list of files and directories that match the given pattern. + + Patterns match paths relative to the workspace root. + + 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) -> str: + """A unique identifier for this Workspace. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + def migrate(self) -> "WorkspaceMigration": + """Plan the explicit migration needed for the current workspace. + + The returned plan has an empty changeset and no steps when no + migration is needed. + """ + _args: list[Arg] = [] + _ctx = self._select("migrate", _args) + return WorkspaceMigration(_ctx) + + def module(self, name: str) -> "WorkspaceModule": + """Return a module defined in the workspace configuration. + + Reflects the selected env's effective view. + + Parameters + ---------- + name: + Module name to inspect. + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("module", _args) + return WorkspaceModule(_ctx) + + def module_source(self, path: str) -> ModuleSource: + """Load a module source from a path within the workspace. + + Relative paths (e.g., "foo") resolve from the workspace cwd; absolute + paths (e.g., "/foo") resolve from the workspace root. + + Fails if the path does not point to an initialized module. + + Parameters + ---------- + path: + Location of the module source to load, relative to the workspace + cwd or absolute from the workspace root. + """ + _args = [ + Arg("path", path), + ] + _ctx = self._select("moduleSource", _args) + return ModuleSource(_ctx) + + async def modules(self) -> list["WorkspaceModule"]: + """List modules defined in the workspace configuration. + + Reflects the selected env's effective view. + """ + _args: list[Arg] = [] + _ctx = self._select("modules", _args) + return await _ctx.execute_object_list(WorkspaceModule) + + def reloaded(self) -> Self: + """Return this workspace with its cached host reads invalidated, so + subsequent file and directory reads re-read the live host instead of a + snapshot cached earlier in the session. + """ + _args: list[Arg] = [] + _ctx = self._select("reloaded", _args) + return Workspace(_ctx) + + def sdk(self, name: str) -> "WorkspaceSDK": + """An installed SDK, by name. + + Parameters + ---------- + name: + SDK name to look up. + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("sdk", _args) + return WorkspaceSDK(_ctx) + + async def sdks(self) -> list["WorkspaceSDK"]: + """Installed SDKs.""" + _args: list[Arg] = [] + _ctx = self._select("sdks", _args) + return await _ctx.execute_object_list(WorkspaceSDK) + + async def search(self, pattern: str, *, paths: list[str] | None = None, globs: list[str] | None = None, literal: bool | None = False, multiline: bool | None = False, dotall: bool | None = False, insensitive: bool | None = False, skip_ignored: bool | None = False, skip_hidden: bool | None = False, files_only: bool | None = False, limit: int | None = None,) -> list[SearchResult]: + """Searches for content matching the given regular expression or literal + string. + + Uses Rust regex syntax; escape literal ., [, ], {, }, | with + backslashes. + + Runs ripgrep on the client host, falling back to grep if unavailable. + + Parameters + ---------- + pattern: + The text to match. + paths: + Directory or file paths to search + globs: + Glob patterns to match (e.g., "*.md") + literal: + Interpret the pattern as a literal string instead of a regular + expression. + multiline: + Enable searching across multiple lines. + dotall: + Allow the . pattern to match newlines in multiline mode. + insensitive: + Enable case-insensitive matching. + skip_ignored: + Honor .gitignore, .ignore, and .rgignore files. + skip_hidden: + Skip hidden files (files starting with .). + files_only: + Only return matching files, not lines and content + limit: + Limit the number of results to return + """ + _args = [ + Arg("pattern", pattern), + Arg("paths", [] if paths is None else paths, []), + Arg("globs", [] if globs is None else globs, []), + Arg("literal", literal, False), + Arg("multiline", multiline, False), + Arg("dotall", dotall, False), + Arg("insensitive", insensitive, False), + Arg("skipIgnored", skip_ignored, False), + Arg("skipHidden", skip_hidden, False), + Arg("filesOnly", files_only, False), + Arg("limit", limit, None), + ] + _ctx = self._select("search", _args) + return await _ctx.execute_object_list(SearchResult) + + def services(self, *, include: list[str] | None = None,) -> UpGroup: + """Return all services from modules loaded in the workspace. + + Parameters + ---------- + include: + Only include services matching the specified patterns + """ + _args = [ + Arg("include", include, None), + ] + _ctx = self._select("services", _args) + return UpGroup(_ctx) + + def with_changes(self, changes: Changeset) -> Self: + """Return this workspace with a changeset applied, without mutating the + source. + + Parameters + ---------- + changes: + Changes to apply. + """ + _args = [ + Arg("changes", changes), + ] + _ctx = self._select("withChanges", _args) + return Workspace(_ctx) + + def with_config_env(self, name: str, *, here: bool | None = False,) -> Self: + """Return this workspace with a named config environment created. + + Parameters + ---------- + name: + Environment name. + here: + Write to the workspace config directory at the workspace cwd. + """ + _args = [ + Arg("name", name), + Arg("here", here, False), + ] + _ctx = self._select("withConfigEnv", _args) + return Workspace(_ctx) + + def with_config_value(self, key: str, value: str, *, values: list[str] | None = None, here: bool | None = False,) -> Self: + """Return this workspace with a configuration value written. + + When the session selects an env, the key is scoped to that env's + overlay and the env is created if missing. + + Parameters + ---------- + key: + Dotted key path. + value: + Value to set. Bools, integers, and comma-separated arrays are + auto-detected. + values: + List value to set. Elements are stored verbatim, with no auto- + detection. Mutually exclusive with value. + here: + Write to the workspace config directory at the workspace cwd. + """ + _args = [ + Arg("key", key), + Arg("value", value), + Arg("values", values, None), + Arg("here", here, False), + ] + _ctx = self._select("withConfigValue", _args) + return Workspace(_ctx) + + def with_init_client(self, path: str, sdk: str, module: str, *, args: JSON | None = None, here: bool | None = False, no_generate: bool | None = False,) -> Self: + """Return this workspace with a generated API client initialized. + + The SDK's generators run for the new client, so the returned workspace + carries its generated bindings. + + Parameters + ---------- + path: + Output directory for the generated client, relative to the + workspace cwd; a leading "/" is relative to the workspace root. + sdk: + Workspace SDK name or module entry name to use. + module: + Workspace-relative path or canonical ref for the module the client + binds to. + args: + SDK-specific init arguments. + here: + Write to the workspace config directory at the workspace cwd. + no_generate: + Skip running the SDK's generators for the new client. + """ + _args = [ + Arg("path", path), + Arg("sdk", sdk), + Arg("module", module), + Arg("args", args, None), + Arg("here", here, False), + Arg("noGenerate", no_generate, False), + ] + _ctx = self._select("withInitClient", _args) + return Workspace(_ctx) + + def with_init_module(self, name: str, sdk: str, *, path: str | None = '', source: str | None = '', include: list[str] | None = None, args: JSON | None = None, here: bool | None = False, no_generate: bool | None = False,) -> Self: + """Return this workspace with a new module initialized. + + The SDK's generators run for the new module, so the returned workspace + carries the generated code it needs to be loadable. + + Parameters + ---------- + name: + Name of the new module. + sdk: + Workspace SDK name or module entry name to use. + path: + Path for the new module, relative to the workspace cwd; a leading + "/" is relative to the workspace root. Defaults to + .dagger/modules/ beside the workspace config. + source: + Source subpath within the new module. + include: + Additional include patterns for the module. + args: + SDK-specific init arguments. + here: + Write to the workspace config directory at the workspace cwd. + no_generate: + Skip running the SDK's generators for the new module. + """ + _args = [ + Arg("name", name), + Arg("sdk", sdk), + Arg("path", path, ''), + Arg("source", source, ''), + Arg("include", [] if include is None else include, []), + Arg("args", args, None), + Arg("here", here, False), + Arg("noGenerate", no_generate, False), + ] + _ctx = self._select("withInitModule", _args) + return Workspace(_ctx) + + def with_module(self, ref: str, *, name: str | None = '', here: bool | None = False,) -> Self: + """Return this workspace with a module installed in its config. + + When the session selects an env, the module is recorded in that env's + overlay and the env is created if missing. + + Parameters + ---------- + ref: + Module reference to install. + name: + Override name for the installed module entry. + here: + Write to the workspace config directory at the workspace cwd. + """ + _args = [ + Arg("ref", ref), + Arg("name", name, ''), + Arg("here", here, False), + ] + _ctx = self._select("withModule", _args) + return Workspace(_ctx) + + def with_mounted_directory(self, path: str, source: Directory) -> Self: + """Return this workspace with a directory mounted read-only at the given + path, without mutating the source. + + Mounted content is readable through the normal workspace file tools + but shadows the source at the mount path and stays out of the pending + changeset: it never appears in changes, is never exported, and cannot + be modified. + + Parameters + ---------- + path: + Location of the mounted directory. Relative paths resolve from the + workspace cwd. + source: + Directory to mount. + """ + _args = [ + Arg("path", path), + Arg("source", source), + ] + _ctx = self._select("withMountedDirectory", _args) + return Workspace(_ctx) + + def with_mounted_file(self, path: str, source: File) -> Self: + """Return this workspace with a file mounted read-only at the given path, + without mutating the source. + + Mounted content is readable through the normal workspace file tools + but shadows the source at the mount path and stays out of the pending + changeset: it never appears in changes, is never exported, and cannot + be modified. + + Parameters + ---------- + path: + Location of the mounted file. Relative paths resolve from the + workspace cwd. + source: + File to mount. + """ + _args = [ + Arg("path", path), + Arg("source", source), + ] + _ctx = self._select("withMountedFile", _args) + return Workspace(_ctx) + + def with_new_directory(self, path: str, source: Directory) -> Self: + """Return this workspace with a directory added, without mutating the + source. + + Parameters + ---------- + path: + Path of the added directory. Relative paths resolve from the + workspace cwd. + source: + Directory to add. + """ + _args = [ + Arg("path", path), + Arg("source", source), + ] + _ctx = self._select("withNewDirectory", _args) + return Workspace(_ctx) + + def with_new_file(self, path: str, contents: str, *, permissions: int | None = 420,) -> Self: + """Return this workspace with a new or replaced file, without mutating + the source. + + Parameters + ---------- + path: + Path of the new file. Relative paths resolve from the workspace + cwd. + contents: + Contents of the new file. + permissions: + Permissions of the new file. + """ + _args = [ + Arg("path", path), + Arg("contents", contents), + Arg("permissions", permissions, 420), + ] + _ctx = self._select("withNewFile", _args) + return Workspace(_ctx) + + def with_sdk(self, ref: str, *, name: str | None = '', here: bool | None = False, as_sdk_name: str | None = '',) -> Self: + """Return this workspace with an SDK installed in its config. + + Parameters + ---------- + ref: + SDK module reference to install. + name: + Override name for the installed SDK entry. + here: + Write to the workspace config directory at the workspace cwd. + as_sdk_name: + User-facing SDK name to persist under `[modules..as-sdk] + name = ...`. + """ + _args = [ + Arg("ref", ref), + Arg("name", name, ''), + Arg("here", here, False), + Arg("asSdkName", as_sdk_name, ''), + ] + _ctx = self._select("withSDK", _args) + return Workspace(_ctx) + + def with_updated_lock(self) -> Self: + """Return this workspace with refreshed lockfile state.""" + _args: list[Arg] = [] + _ctx = self._select("withUpdatedLock", _args) + return Workspace(_ctx) + + def with_workdir(self, path: str) -> Self: + """Return this workspace with its working directory pointed at the given + workspace-relative path. + + Parameters + ---------- + path: + Workspace-relative path to use as the working directory. + """ + _args = [ + Arg("path", path), + ] + _ctx = self._select("withWorkdir", _args) + return Workspace(_ctx) + + def without_config_env(self, name: str, *, here: bool | None = False,) -> Self: + """Return this workspace with a named config environment removed. + + Parameters + ---------- + name: + Environment name. + here: + Write to the workspace config directory at the workspace cwd. + """ + _args = [ + Arg("name", name), + Arg("here", here, False), + ] + _ctx = self._select("withoutConfigEnv", _args) + return Workspace(_ctx) + + def without_config_value(self, key: str, *, here: bool | None = False,) -> Self: + """Return this workspace with a configuration value removed. + + Errors when the key is not currently set. + + When the session selects an env, the key is scoped to that env's + overlay. + + Parameters + ---------- + key: + Dotted key path (e.g. modules.greeter.settings.greeting). + here: + Write to the workspace config directory at the workspace cwd. + """ + _args = [ + Arg("key", key), + Arg("here", here, False), + ] + _ctx = self._select("withoutConfigValue", _args) + return Workspace(_ctx) + + def without_directory(self, path: str) -> Self: + """Return this workspace with a directory removed, without mutating the + source. + + Parameters + ---------- + path: + Path of the directory to remove. Relative paths resolve from the + workspace cwd. + """ + _args = [ + Arg("path", path), + ] + _ctx = self._select("withoutDirectory", _args) + return Workspace(_ctx) + + def without_file(self, path: str) -> Self: + """Return this workspace with a file removed, without mutating the + source. + + Parameters + ---------- + path: + Path of the file to remove. Relative paths resolve from the + workspace cwd. + """ + _args = [ + Arg("path", path), + ] + _ctx = self._select("withoutFile", _args) + return Workspace(_ctx) + + def without_module(self, name: str, *, here: bool | None = False,) -> Self: + """Return this workspace with a module removed from its config. + + When the session selects an env, only that env's overlay entry is + removed. + + Parameters + ---------- + name: + Name of the installed module entry to remove. + here: + Write to the workspace config directory at the workspace cwd. + """ + _args = [ + Arg("name", name), + Arg("here", here, False), + ] + _ctx = self._select("withoutModule", _args) + return Workspace(_ctx) + + def without_sdk(self, name: str, *, here: bool | None = False,) -> Self: + """Return this workspace with an SDK removed from its config. + + Parameters + ---------- + name: + Name of the installed SDK entry to remove. + here: + Write to the workspace config directory at the workspace cwd. + """ + _args = [ + Arg("name", name), + Arg("here", here, False), + ] + _ctx = self._select("withoutSDK", _args) + return Workspace(_ctx) + + def with_(self, cb: Callable[["Workspace"], "Workspace"]) -> "Workspace": + """Call the provided callable with current Workspace. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + + +@typecheck +class WorkspaceGit(Type): + """Local git state for a workspace.""" + + def head(self) -> GitRef: + """The checked-out HEAD of this workspace.""" + _args: list[Arg] = [] + _ctx = self._select("head", _args) + return GitRef(_ctx) + + async def id(self) -> str: + """A unique identifier for this WorkspaceGit. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + def uncommitted(self) -> Changeset: + """Uncommitted changes in this workspace, using the same rules as + GitRepository.uncommitted. + """ + _args: list[Arg] = [] + _ctx = self._select("uncommitted", _args) + return Changeset(_ctx) + + +@typecheck +class WorkspaceMigration(Type): + """A planned workspace migration.""" + + def changes(self) -> Changeset: + """Filesystem changes for the full migration plan.""" + _args: list[Arg] = [] + _ctx = self._select("changes", _args) + return Changeset(_ctx) + + async def id(self) -> str: + """A unique identifier for this WorkspaceMigration. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def steps(self) -> list["WorkspaceMigrationStep"]: + """Logical migration steps, each identified by a stable code.""" + _args: list[Arg] = [] + _ctx = self._select("steps", _args) + return await _ctx.execute_object_list(WorkspaceMigrationStep) + + +@typecheck +class WorkspaceMigrationStep(Type): + """A single logical part of a workspace migration.""" + + def changes(self) -> Changeset: + """Filesystem changes for this step.""" + _args: list[Arg] = [] + _ctx = self._select("changes", _args) + return Changeset(_ctx) + + async def code(self) -> str: + """Stable code identifying this logical migration step. + + 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("code", _args) + return await _ctx.execute(str) + + async def description(self) -> str: + """Generic summary of this step's purpose and impact. + + 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) -> str: + """A unique identifier for this WorkspaceMigrationStep. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def warnings(self) -> list[str]: + """Non-fatal warnings raised while planning this step. + + 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("warnings", _args) + return await _ctx.execute(list[str]) + + +@typecheck +class WorkspaceModule(Type): + """A module entry in the workspace configuration.""" + + async def entrypoint(self) -> bool: + """Whether the module is the workspace entrypoint (functions aliased to + Query root). + + 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("entrypoint", _args) + return await _ctx.execute(bool) + + async def id(self) -> str: + """A unique identifier for this WorkspaceModule. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def name(self) -> str: + """The module 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 settings(self) -> list["WorkspaceModuleSetting"]: + """List constructor-backed settings for this module.""" + _args: list[Arg] = [] + _ctx = self._select("settings", _args) + return await _ctx.execute_object_list(WorkspaceModuleSetting) + + async def source(self) -> str: + """The module source path. + + 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 WorkspaceModuleSetting(Type): + """A constructor-backed module setting.""" + + async def description(self) -> str: + """The constructor argument description. + + 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) -> str: + """A unique identifier for this WorkspaceModuleSetting. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def is_list(self) -> bool: + """Whether the setting accepts a list of values. + + 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("isList", _args) + return await _ctx.execute(bool) + + async def key(self) -> str: + """The setting key. + + 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("key", _args) + return await _ctx.execute(str) + + async def value(self) -> str: + """The configured value after applying the selected workspace + environment, or empty when unset. + + 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 WorkspaceSDK(Type): + """An installed SDK: a module marked for scaffolding other modules and + clients.""" + + async def clients(self) -> list[WorkspaceModule]: + """Clients generated with this SDK.""" + _args: list[Arg] = [] + _ctx = self._select("clients", _args) + return await _ctx.execute_object_list(WorkspaceModule) + + async def id(self) -> str: + """A unique identifier for this WorkspaceSDK. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def modules(self) -> list[WorkspaceModule]: + """Modules authored with this SDK.""" + _args: list[Arg] = [] + _ctx = self._select("modules", _args) + return await _ctx.execute_object_list(WorkspaceModule) + + async def name(self) -> str: + """The user-facing SDK 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 ref(self) -> str: + """The module reference this SDK was installed from. + + 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) + + + +class Client(Query): + """The Dagger client. + + Inherits all Query API methods and adds connection management. + """ + +dag = Client() +"""The global client instance.""" + +__all__ = [ + "Address", + "Agent", + "AgentGroup", + "BuildArg", + "CacheSharingMode", + "CacheVolume", + "Changeset", + "ChangesetMergeConflict", + "ChangesetsMergeConflict", + "Check", + "CheckGroup", + "Client", + "ClientFilesyncMirror", + "Cloud", + "Container", + "CurrentModule", + "CurrentModuleAsSDK", + "CurrentModuleAsSDKClient", + "CurrentModuleAsSDKModule", + "DiffStat", + "DiffStatKind", + "Directory", + "EnumTypeDef", + "EnumValueTypeDef", + "EnvFile", + "EnvVariable", + "Error", + "ErrorValue", + "ExistsType", + "Exportable", + "FieldTypeDef", + "File", + "FileType", + "Function", + "FunctionArg", + "FunctionCachePolicy", + "FunctionCall", + "FunctionCallArgValue", + "GeneratedCode", + "Generator", + "GeneratorGroup", + "GitCommit", + "GitRef", + "GitRepository", + "HTTPState", + "HealthcheckConfig", + "ImageLayerCompression", + "ImageMediaTypes", + "InputTypeDef", + "InterfaceTypeDef", + "JSON", + "JSONValue", + "LLM", + "LLMContentBlock", + "LLMContentBlockInput", + "LLMContentBlockKind", + "LLMMessage", + "LLMMessageRole", + "LLMSkill", + "LLMTokenUsage", + "Label", + "ListTypeDef", + "Module", + "ModuleConfigClient", + "ModuleSource", + "ModuleSourceExperimentalFeature", + "ModuleSourceKind", + "NetworkProtocol", + "Node", + "ObjectTypeDef", + "PatchConflict", + "PipelineLabel", + "Platform", + "Port", + "PortForward", + "Query", + "RegistryProtocol", + "RemoteGitMirror", + "ReturnType", + "SDKConfig", + "ScalarTypeDef", + "Schema", + "SearchResult", + "SearchSubmatch", + "Secret", + "Service", + "Socket", + "SourceMap", + "Stat", + "Syncer", + "Terminal", + "TypeDef", + "TypeDefKind", + "Up", + "UpGroup", + "Void", + "Volume", + "Workspace", + "WorkspaceGit", + "WorkspaceMigration", + "WorkspaceMigrationStep", + "WorkspaceModule", + "WorkspaceModuleSetting", + "WorkspaceSDK", + "dag", +] \ No newline at end of file diff --git a/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/log.py b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/log.py new file mode 100644 index 0000000..1d0d7ee --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/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/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/__init__.py b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/__init__.py new file mode 100644 index 0000000..1ab0293 --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/__init__.py @@ -0,0 +1,47 @@ +from typing_extensions import Doc + +from dagger.mod._arguments import DefaultAddress +from dagger.mod._arguments import DefaultPath +from dagger.mod._arguments import Deprecated +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() + +agent = _default_mod.agent +check = _default_mod.check +enum_type = _default_mod.enum_type +function = _default_mod.function +field = _default_mod.field +generate = _default_mod.generate +interface = _default_mod.interface +object_type = _default_mod.object_type +up = _default_mod.up + + +def default_module() -> Module: + """Return the default Module builder instance.""" + return _default_mod + + +__all__ = [ + "DefaultAddress", + "DefaultPath", + "Deprecated", + "Doc", # Only re-exported because it's in `typing_extensions`. + "Enum", + "Ignore", + "Name", + "agent", + "check", + "enum_type", + "field", + "function", + "generate", + "interface", + "object_type", + "up", +] diff --git a/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/_arguments.py b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/_arguments.py new file mode 100644 index 0000000..ccf8c05 --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/_arguments.py @@ -0,0 +1,217 @@ +import dataclasses +import inspect +import logging + +from cattrs.preconf.json import JsonConverter + +import dagger +from dagger.mod._exceptions import BadUsageError +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 DefaultAddress: + """If the argument is omitted, load it from the given container address. + + Only applies to arguments of type :py:class:`dagger.Container`. + + 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, ctr: Annotated[dagger.Container, DefaultAddress("alpine:latest")] + ): ... + """ + + address: str + + def __str__(self) -> str: + return self.address + + +@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`/:py:class:`dagger.File` + or :py:class:`dagger.GitRepository`/:py:class:`dagger.GitRef`. + + + 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("..")]): ... + + + @function + def build(self, src: Annotated[dagger.GitRef, DefaultPath("./.git")]): ... + """ + + 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, frozen=True) +class Deprecated: + """Mark a function argument as deprecated. + + Example usage:: + + @function + def old(self, value: Annotated[str, Deprecated("Use new instead")]): ... + """ + + reason: str = "" + + def __str__(self) -> str: + return self.reason + + +@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_address: str | None = None + default_value: dagger.JSON | None = None + deprecated: str | 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 any( + [ + self.has_default, + self.default_path is not None, + self.default_address is not None, + self.is_nullable, + ] + ) + + def _validate(self): + extra = {"parameter": self.signature} + + # 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 BadUsageError(msg, extra=extra) + + if self.default_path: + if self.has_default and not ( + self.is_nullable and self.signature.default is None + ): + msg = ( + f"DefaultPath can't be used in parameter '{self.signature.name}' " + "since it already defines a default value." + ) + raise BadUsageError(msg, extra=extra) + + 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 BadUsageError(msg, extra=extra) + + if self.deprecated and not self.is_optional: + msg = ( + f"Can't deprecate required parameter '{self.signature.name}'. " + "Mark it optional or provide a default value." + ) + raise BadUsageError(msg, extra=extra) diff --git a/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/_converter.py b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/_converter.py new file mode 100644 index 0000000..272ce7a --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/_converter.py @@ -0,0 +1,222 @@ +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() + + 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._ctx.select_id(cls._graphql_name(), 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, context: str = "type") -> "TypeDef": # noqa: C901, PLR0911 + """Convert Python object to API type.""" + if is_initvar(annotation): + return to_typedef(annotation.type, context) + + if is_annotated(annotation): + return to_typedef(strip_annotations(annotation), context) + + td = dag.type_def() + + typ = TypeHint(annotation) + error_msg = f"unsupported {context}: {typ.hint!r}" + + if is_nullable(typ): + td = td.with_optional(True) + + typ = non_null(typ) + + # Can't represent unions in the API. + if is_union(typ): + raise TypeError(error_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) + + raise TypeError(error_msg) diff --git a/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/_exceptions.py b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/_exceptions.py new file mode 100644 index 0000000..a55b09b --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/_exceptions.py @@ -0,0 +1,188 @@ +import json +import logging +import traceback +from collections.abc import Mapping +from typing import Any + +import cattrs +from opentelemetry.semconv.attributes.exception_attributes import ( + EXCEPTION_MESSAGE, + EXCEPTION_STACKTRACE, + EXCEPTION_TYPE, +) + +import dagger +from dagger import DaggerError, dag, telemetry + +logger = logging.getLogger(__package__) + + +class ModuleError(DaggerError): + """Base class for all errors raised by modules. + + This class flags to the entrypoint that the error has been handled so we + can exit cleanly, without dumping the full traceback even when it's not + useful. + + It also allows control over what gets reported to dag.error() via the + error message and extra values. + """ + + def __init__(self, /, *args, extra: Mapping[str, Any] | None = None): + super().__init__(*args) + self.extra = extra + + +class ModuleLoadError(ModuleError): + """Error while loading Python module with functions.""" + + +class InvalidInputError(ModuleError): + """Error while deserializing values into Python objects. + + If it happens it's probably a bug in the SDK because the API should + validate early if input is not of expected type. + """ + + +class InvalidResultError(ModuleError): + """Error while serializing Python values into JSON.""" + + +class ObjectNotFoundError(ModuleError): + """Parent object not found on registry.""" + + +class RegistrationError(ModuleError): + """An error caused by an invalid type def registration.""" + + +class BadUsageError(ModuleError): + """A usage error.""" + + +class FunctionError(ModuleError): + """An error while executing a user function.""" + + +def transform_error( + exc: Exception, + msg: str = "", + origin: Any | None = None, + typ: type | None = None, +) -> str: + """Transform an exception raised by cattrs into an error message.""" + kwargs = {} + if origin is not None: + path = getattr(origin, "__qualname__", "") + if hasattr(origin, "__module__"): + path = f"{origin.__module__}.{path}" + + if path: + kwargs["path"] = path + + if msg: + msg += ": " + + # cattrs.transform_error sets expected type as None when not a cattrs exception. + if typ is not None and not isinstance(exc, cattrs.BaseValidationError): + msg += cattrs.v.format_exception(exc, typ) + if path := kwargs.get("path"): + msg = f"{msg} @ {path}" + else: + msg += "; ".join( + error.removesuffix(" $").removesuffix(" @") + for error in cattrs.transform_error(exc, **kwargs) + ) + + return msg + + +def log_exception_only( + exc: Exception, + msg: str, + *args, + # Extra note to add to the end of the error message when shown on + # the log, but not included in the dagger.Error message. + note: str = "", +): + """Log just this exception, without full traceback. + + Generates an error log message just for this exception, not the full + traceback, including without any chained exceptions. + + This should be used in cases where this exception has enough information + or when the rest of the traceback doesn't add anything particularly useful, + so there's less noise to sort through while debugging. + + The full traceback will still be included in dag.Error() values which + could at some point be optionally shown in the web/cloud UI at some + point, but it's also fully available to LLM in the meantim. + """ + if note and hasattr(exc, "add_note"): + exc.add_note(note) + logger.error(msg, *args, exc_info=(type(exc), exc, None)) + + +async def record_exception(exc: Exception): + """Convert a Python exception into a `dagger.Error`.""" + attrs: dict[str, Any] = _exception_attributes(exc) + msg = f"{attrs[EXCEPTION_TYPE]}: {attrs[EXCEPTION_MESSAGE]}" + + if isinstance(exc, ModuleError) and exc.extra: + extra = {f"extra.{key}": val for key, val in exc.extra.items()} + # ModuleError extra values don't conflict with the OTel attributes + # but prepending like this avoids a future mistake. + attrs = {**extra, **attrs} + + # Preserve original API error so it's properly propagated. + if isinstance(exc, dagger.QueryError): + msg = str(exc) + attrs.update(exc.error.extensions) + + dag_err = dag.error(msg) + for key, value in attrs.items(): + dag_err = dag_err.with_value(key, dagger.JSON(_safe_json_dumps(value))) + + await dag.current_function_call().return_error(dag_err) + + # When an error occurs within a started span context the OTel SDK + # automatically sends an event with details about the exception. + # Switching to dag.Error doesn't take advantage of that and the engine + # doesn't recreate the exception event on the parent function span. + # Still, recording the exception manually can be useful when analyzing the + # raw telemetry in e.g., Honeycomb. + with telemetry.get_tracer().start_as_current_span( + "recording Python exception", + # TODO: even with following attribute it's still being shown in the + # Cloud UI. + attributes={"dagger.io/ui.internal": True}, + ) as span: + span.record_exception(exc) + + +def _exception_attributes(exc: Exception) -> dict[str, str]: + message = str(exc) + stacktrace = "".join(traceback.format_exception(exc)) + module = type(exc).__module__ + qualname = type(exc).__qualname__ + exc_type = f"{module}.{qualname}" if module and module != "builtins" else qualname + + # hide the full `dagger.mod._exception` module path + exc_type = exc_type.replace(__name__ + ".", "") + + # Reusing OTel attribute names for consistency. + return { + EXCEPTION_TYPE: exc_type, + EXCEPTION_MESSAGE: message, + EXCEPTION_STACKTRACE: stacktrace, + } + + +def _safe_json_dumps(value: Any) -> str: + """Safely serialize value to JSON, falling back to repr() if not serializable.""" + try: + return json.dumps(value) + except (TypeError, ValueError): + # Fall back to string representation for non-serializable values + return json.dumps(repr(value)) diff --git a/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/_module.py b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/_module.py new file mode 100644 index 0000000..16e730e --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/_module.py @@ -0,0 +1,1042 @@ +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._converter import make_converter, to_typedef +from dagger.mod._exceptions import ( + BadUsageError, + FunctionError, + InvalidInputError, + InvalidResultError, + ObjectNotFoundError, + RegistrationError, + log_exception_only, + transform_error, +) +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, + extract_enum_member_doc, + get_doc, + get_parent_module_doc, + is_annotated, +) + +logger = logging.getLogger(__package__) + +OBJECT_DEF_KEY: typing.Final[str] = "__dagger_object__" +FIELD_DEF_KEY: typing.Final[str] = "__dagger_field__" +FUNCTION_DEF_KEY: typing.Final[str] = "__dagger_function__" +CHECK_DEF_KEY: typing.Final[str] = "__dagger_check__" +GENERATOR_DEF_KEY: typing.Final[str] = "__dagger_generate__" +UP_DEF_KEY: typing.Final[str] = "__dagger_up__" +AGENT_DEF_KEY: typing.Final[str] = "__dagger_agent__" +MODULE_NAME: typing.Final[str] = os.getenv("DAGGER_MODULE", "") +MAIN_OBJECT: typing.Final[str] = os.getenv("DAGGER_MAIN_OBJECT", "") +TYPE_DEF_FILE: typing.Final[str] = os.getenv("DAGGER_MODULE_FILE", "/module.json") + +T = TypeVar("T", bound=type) + + +class Module: + """Builder for a :py:class:`dagger.Module`.""" + + def __init__(self, main_name: str = MAIN_OBJECT): + self._main_name = main_name + self._converter: JsonConverter = make_converter() + self._objects: dict[str, ObjectType] = {} + self._enums: dict[str, type[enum.Enum]] = {} + self._main: ObjectType | None = None + # Escape hatch if there's too much noise from showing stack traces + # from exceptions raised in functions by default. Not documented + # intentionally for now. + self.log_exceptions = True + + @property + def main_cls(self) -> type[ObjectType]: + 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 + + async def serve(self): + if await dag.current_function_call().parent_name(): + result = await self.invoke() + else: + try: + result = await self._typedefs() + except TypeError as e: + raise RegistrationError(str(e)) from e + + try: + output = json.dumps(result) + except (TypeError, ValueError) as e: + # Not expected to happen because unstructuring should reduce + # Python complex types to primitive values that are easily + # serialized to JSON. If not, it's something that should be caught + # earlier. + msg = f"Failed to serialize final result as JSON: {e}" + raise InvalidResultError(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): + """Register the module and its types with the Dagger API.""" + try: + result = await self._typedefs() + output = json.dumps(result) + except TypeError as e: + raise RegistrationError(str(e), e) from e + await anyio.Path(TYPE_DEF_FILE).write_text(output) + + async def _typedefs(self) -> str: # noqa: C901, PLR0912, PLR0915 + if not self._main_name: + msg = "Main object name can't be empty" + raise ValueError(msg) + try: + self.get_object(self._main_name) + except ObjectNotFoundError as e: + msg = ( + f"Main object with name '{self._main_name}' not found or class not " + "decorated with '@dagger.object_type'\n" + f"If you believe the module name '{MODULE_NAME}' is incorrectly " + "being converted into PascalCase, please file a bug report." + ) + raise ObjectNotFoundError(msg, extra=e.extra) from None + + 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), + deprecated=obj_type.deprecated, + ) + + # Object fields + if obj_type.fields: + types = typing.get_type_hints(obj_type.cls) + + for field_name, field in obj_type.fields.items(): + ctx = f"type for field '{field.original_name}' in {obj_type}" + type_def = type_def.with_field( + field_name, + to_typedef(types[field.original_name], ctx), + description=get_doc(field.return_type), + deprecated=field.meta.deprecated, + ) + + # Object/interface functions + for func_name, func in obj_type.functions.items(): + what = f"function '{func_name}'" if func_name else "constructor" + + func_def = dag.function( + func_name, + to_typedef( + func.return_type, + f"return type for {what} in {obj_type}", + ), + ) + + if doc := func.doc: + func_def = func_def.with_description(doc) + + if func.cache_policy is not None: + if func.cache_policy == "never": + func_def = func_def.with_cache_policy( + dagger.FunctionCachePolicy.Never, + ) + elif func.cache_policy == "session": + func_def = func_def.with_cache_policy( + dagger.FunctionCachePolicy.PerSession, + ) + elif func.cache_policy != "": + func_def = func_def.with_cache_policy( + dagger.FunctionCachePolicy.Default, + time_to_live=func.cache_policy, + ) + if deprecated := func.deprecated: + func_def = func_def.with_deprecated(reason=deprecated) + if func.check: + func_def = func_def.with_check() + if func.generate: + func_def = func_def.with_generator() + if func.service: + func_def = func_def.with_up() + if func.agent: + func_def = func_def.with_agent() + + for param in func.parameters.values(): + arg_def = to_typedef( + param.resolved_type, + f"parameter type for '{param.name}' in {what} and {obj_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, + default_address=param.default_address, + ignore=param.ignore, + deprecated=param.deprecated, + ) + + type_def = ( + type_def.with_constructor(func_def) + if func_name == "" + else type_def.with_function(func_def) + ) + + # Add object/interface to module + mod = ( + mod.with_interface(type_def) + if obj_type.interface + else 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: + description = getattr(member, "description", None) + meta = member_docs.get(member.name) + + if description is None and meta and meta.description is not None: + description = meta.description + + enum_def = enum_def.with_enum_member( + member.name, + value=str(member.value), + description=description, + deprecated=meta.deprecated if meta else None, + ) + mod = mod.with_enum(enum_def) + + return await mod.id() + + async def invoke(self) -> str: + """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() + parent_name = await fn_call.parent_name() + + if not parent_name: + msg = ( + "Seems like the SDK module isn't registering the types correctly. " + "This is a bug." + ) + raise RegistrationError(msg) + + 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: + logger.exception("Failed to decode JSON parent value") + msg = "Unable to decode the parent object's state" + extra = { + "parent_json": parent_json, + } + raise InvalidInputError(msg, extra=extra) 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: + logger.exception("Failed to decode JSON input value") + msg = f"Unable to decode input argument '{arg_name}'" + extra = { + "json_value": arg_value, + } + raise InvalidInputError(msg, extra=extra) 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_result( + self, + parent_name: str, + parent_state: Mapping[str, Any], + name: str, + raw_inputs: Mapping[str, Any], + ) -> Any: + """Get function result as an unstructured Python primitive.""" + result, fn = await self.get_structured_result( + parent_name, + parent_state, + name, + raw_inputs, + ) + if fn.return_type is not None: + try: + return await self.unstructure(result, fn.return_type) + except Exception as e: + log_exception_only(e, "Invalid result from function") + msg = transform_error( + e, + origin=getattr(fn, "wrapped", None), + typ=fn.return_type, + ) + msg += ( + "\n" + "Please check if the returned value at runtime matches " + "the function's declared return type." + ) + raise InvalidResultError(msg) from e + return None + + async def get_structured_result( + self, + parent_name: str, + parent_state: Mapping[str, Any], + name: str, + raw_inputs: Mapping[str, Any], + ) -> tuple[Any, Field | Function]: + """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, 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 + + fn = obj_type.get_bound_function(parent, name) + + inputs = await self._convert_inputs(fn, 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)) + + result = await self.call(fn.wrapped, *bound.args, **bound.kwargs) + + # Provide better errors for missing async/await + if inspect.iscoroutine(result): + result.close() # avoid RuntimeWarning + + if not inspect.iscoroutinefunction(fn.wrapped): + msg = ( + f"Function '{fn}' returned a coroutine.\n" + "Did you forget to add 'async' to the function signature?" + ) + else: + msg = ( + f"Async function '{fn}' was never awaited.\n" + "Did you forget to add an 'await' to the return value?" + ) + raise FunctionError(msg) from None + + return result, fn + + async def call(self, func: Func[P, R], *args: P.args, **kwargs: P.kwargs) -> R: + """Call a function and return its result.""" + try: + # We could await based on the return value instead of checking function + # color but that would silently allow incorrect code which is + # especially bad if not intentional and we don't warn user about it. + result = func(*args, **kwargs) + if inspect.iscoroutinefunction(func): + result = await cast(typing.Awaitable[R], result) + except FunctionError: + # Escape hatch to fully control logging from user code. + raise + except dagger.QueryError as e: + tb = e.__traceback__ + # Exclude the line in "try" above + if tb: + tb = tb.tb_next + # Exclude the underlying TransportQueryError to reduce noise + e.__cause__ = None + logger.exception( + "API error while executing function", + exc_info=(type(e), e, tb), + ) + # Preserve API error so it's properly propagated. + raise e from None + except Exception as e: + # Escape hatch if too noisy. + if self.log_exceptions: + # Logging the exception will show the full stack trace on stderr. + logger.exception("Unhandled exception while executing function") + raise FunctionError(str(e)) from e + + return result + + async def structure(self, obj: Any, cl: type[T]) -> T: + """Convert a primitive value to the expected type.""" + return await asyncify(self._converter.structure, obj, cl) + + async def unstructure(self, obj: Any, unstructure_as: Any) -> Awaitable[Any]: + """Convert a result to primitive values.""" + return await asyncify(self._converter.unstructure, obj, unstructure_as) + + def get_object(self, name: str) -> ObjectType: + """Get the object type definition for the given name.""" + try: + return self._objects[name] + except KeyError: + # Not expected to happen during invoke because registration should + # fail first. + msg = f"No '@dagger.object_type' decorated class named '{name}' was found" + extra = {"objects_found": self._objects.keys()} + raise ObjectNotFoundError(msg, extra=extra) from None + + async def _get_parent_instance( + self, + obj_type: ObjectType[T], + state: Mapping[str, Any], + ) -> T: + """Instantiate the parent object from its state.""" + try: + return await self.structure(state, obj_type.cls) + except Exception as e: + log_exception_only(e, "Failed to instantiate parent object") + msg = transform_error( + e, + f"Failed to instantiate parent object '{obj_type}'", + origin=obj_type.cls, + typ=obj_type.cls, + ) + # If API is able to make the call this is likely a bug in the SDK. + # For example, if the registration phase reports a type that isn't + # compatible with cattrs' converter. + msg += ( + "\n" + "This could be an error in the Python SDK. " + "If so, please file a bug report." + ) + extra = {"object_state": state} + raise InvalidInputError(msg, extra=extra) from e + + async def _convert_inputs( + self, + fn: Function, + 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 fn.parameters.items(): + if param.name not in inputs: + if not param.is_optional: + msg = f"Missing required function argument '{python_name}'" + raise InvalidInputError(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 Exception as e: + log_exception_only( + e, + "Failed to convert from primitive input value for argument '%s'", + param.name, + ) + msg = transform_error( + e, + ( + "Failed to convert from primitive input value for argument " + f"'{param.name}'" + ), + origin=fn.wrapped, + typ=type_, + ) + # Same as before, the API can't reasonably hold a value that + # contradicts its type. + msg += ( + "\n" + "This could be an error in the Python SDK. " + "If so, please file a bug report." + ) + extra = { + "function_name": fn.original_name, + "parameter_name": python_name, + "expected_type": type_, + "actual_type": type(value), + } + raise InvalidInputError(msg, extra=extra) 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, + deprecated: str | None = None, + ) -> 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`. + deprecated: + Optional deprecation message exposed to the engine. + """ + 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, deprecated)}, + kw_only=True, + init=init, + repr=init, # default repr shows field as an __init__ argument + **kwargs, + ) + + def check( + self, + func: Func[P, R] | None = None, + ) -> Func[P, R] | Callable[[Func[P, R]], Func[P, R]]: + """Mark a function as a check. + + Checks are functions that validate conditions and return void/error + to indicate pass/fail. This decorator can be combined with + :py:meth:`function`. + + Example usage:: + + @object_type + class MyModule: + @function + @check + def lint(self) -> str: + return "All checks passed" + + Parameters + ---------- + func: + The function to mark as a check. Should be an instance method in a + class decorated with :py:meth:`object_type`. + """ + + def wrapper(fn: Func[P, R]) -> Func[P, R]: + setattr(fn, CHECK_DEF_KEY, True) + return fn + + return wrapper(func) if func else wrapper + + def generate( + self, + func: Func[P, R] | None = None, + ) -> Func[P, R] | Callable[[Func[P, R]], Func[P, R]]: + """Mark a function as a generator. + + Generators are functions that return a Changeset representing + changes to be applied. This decorator can be combined with + :py:meth:`function`. + + Example usage:: + + @object_type + class MyModule: + @function + @generate + def codegen(self) -> dagger.Changeset: + # Generate code and return changeset + ... + + Parameters + ---------- + func: + The function to mark as a generator. Should be an instance method in a + class decorated with :py:meth:`object_type`. + """ + + def wrapper(fn: Func[P, R]) -> Func[P, R]: + setattr(fn, GENERATOR_DEF_KEY, True) + return fn + + return wrapper(func) if func else wrapper + + def up( + self, + func: Func[P, R] | None = None, + ) -> Func[P, R] | Callable[[Func[P, R]], Func[P, R]]: + """Mark a function as a service for ``dagger up``.""" + + def wrapper(fn: Func[P, R]) -> Func[P, R]: + setattr(fn, UP_DEF_KEY, True) + return fn + + return wrapper(func) if func else wrapper + + def agent( + self, + func: Func[P, R] | None = None, + ) -> Func[P, R] | Callable[[Func[P, R]], Func[P, R]]: + """Mark a function as an agent middleware. + + Agent middlewares take a base :py:class:`dagger.LLM` and return an + ``LLM`` with the module's tools and prompting folded onto it. They are + discovered and composed by ``dagger agent``. Besides the base ``LLM``, + an agent function may not declare any other required argument. This + decorator can be combined with :py:meth:`function`. + + Example usage:: + + @object_type + class MyModule: + @function + @agent + def agent(self, base: dagger.LLM) -> dagger.LLM: + return base.with_tools(dag.current_node()) + + Parameters + ---------- + func: + The function to mark as an agent middleware. Should be an instance + method in a class decorated with :py:meth:`object_type`. + """ + + def wrapper(fn: Func[P, R]) -> Func[P, R]: + setattr(fn, AGENT_DEF_KEY, True) + return fn + + return wrapper(func) if func else wrapper + + @overload + def function( + self, + func: Func[P, R], + *, + name: APIName | None = None, + doc: str | None = None, + deprecated: str | None = None, + ) -> Func[P, R]: ... + + @overload + def function( + self, + *, + name: APIName | None = None, + doc: str | None = None, + deprecated: 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, + cache: str | None = None, + deprecated: 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. + deprecated: + Optional deprecation message exposed to the engine. + """ + + # 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)}." + + # Check if function is marked as a check or generator + check = getattr(func, CHECK_DEF_KEY, False) + generator = getattr(func, GENERATOR_DEF_KEY, False) + service = getattr(func, UP_DEF_KEY, False) + agent = getattr(func, AGENT_DEF_KEY, False) + + meta = FunctionDefinition( + name=name, + doc=doc, + cache=cache, + deprecated=deprecated, + check=check, + generator=generator, + service=service, + agent=agent, + ) + + 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, /, *, deprecated: str | None = None) -> T: ... + + @overload + @dataclass_transform( + kw_only_default=True, + field_specifiers=(function, dataclasses.field, dataclasses.Field), + ) + def object_type(self, *, deprecated: str | None = None) -> Callable[[T], T]: ... + + def object_type( + self, + cls: T | None = None, + *, + deprecated: str | 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" + + + Parameters + ---------- + deprecated: + Optional deprecation message visible when introspecting the module. + """ + + def wrapper(cls: T) -> T: + if not inspect.isclass(cls): + msg = f"Expected a class, got {type(cls)}" + raise BadUsageError(msg) + + # Check for InitVar inside Annotated + 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 BadUsageError(msg) + + wrapped = dataclasses.dataclass(kw_only=True)(cls) + return self._process_type(wrapped, deprecated=deprecated) + + return wrapper(cls) if cls else wrapper + + def _process_type( + self, + cls: T, + *, + interface: bool = False, + deprecated: str | None = None, + ) -> T: + obj_def = ObjectType(cls, interface=interface, deprecated=deprecated) + + cls.__dagger_module__ = self + cls.__dagger_object_type__ = obj_def + self._objects[cls.__name__] = obj_def + if cls.__name__ == self._main_name: + self._main = 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.Enum subclass, got {type(cls)}" + raise BadUsageError(msg) + + if not issubclass(cls, enum.Enum): + msg = f"Class '{cls.__name__}' is not an enum.Enum subclass" + raise BadUsageError(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/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/_resolver.py b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/_resolver.py new file mode 100644 index 0000000..993faff --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/_resolver.py @@ -0,0 +1,316 @@ +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 ( + BadUsageError, + InvalidInputError, + RegistrationError, +) +from dagger.mod._types import APIName, FieldDefinition, FunctionDefinition, PythonName +from dagger.mod._utils import ( + get_alt_constructor, + get_alt_name, + get_default_address, + get_default_path, + get_deprecated, + get_doc, + get_ignore, + is_nullable, + is_self, + list_of, + normalize_name, +) + +CHECK_DEF_KEY: str = "__dagger_check__" +GENERATOR_DEF_KEY: str = "__dagger_generate__" +UP_DEF_KEY: str = "__dagger_up__" +AGENT_DEF_KEY: str = "__dagger_agent__" + +logger = logging.getLogger(__package__) + +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): + if self.origin is not None: + return f"{self.origin.__name__}.{self.original_name}" + return self.original_name + + def __repr__(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) + + @property + def deprecated(self) -> str | None: + """Return the deprecation message for the callable, if any.""" + return self.meta.deprecated + + @property + def check(self) -> bool: + """Indicates whether the function is configured as a check.""" + # Check both the metadata and the attribute to support either decorator order + return self.meta.check or getattr(self.wrapped, CHECK_DEF_KEY, False) + + @property + def generate(self) -> bool: + """Indicates whether the function is configured as a generator.""" + # Check both the metadata and the attribute to support either decorator order + return self.meta.generator or getattr(self.wrapped, GENERATOR_DEF_KEY, False) + + @property + def service(self) -> bool: + """Indicates whether the function is configured as a service for dagger up.""" + # Check both the metadata and the attribute to support either decorator order + return self.meta.service or getattr(self.wrapped, UP_DEF_KEY, False) + + @property + def agent(self) -> bool: + """Indicates whether the function is configured as an agent middleware.""" + # Check both the metadata and the attribute to support either decorator order + return self.meta.agent or getattr(self.wrapped, AGENT_DEF_KEY, False) + + @cached_property + def cache_policy(self): + return self.meta.cache + + @cached_property + def type_hints(self): + return get_type_hints(self.wrapped) + + @cached_property + def type_hints_with_extras(self): + """Type hints with Annotated metadata preserved. + + Used for extracting metadata like DefaultPath, Doc, Name, etc. + from parameters when `from __future__ import annotations` is used. + """ + return get_type_hints(self.wrapped, include_extras=True) + + @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 BadUsageError(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 + + # Get the annotated type (with Annotated preserved) for metadata extraction. + # This is needed when `from __future__ import annotations` is used, + # which causes param.annotation to be a string instead of a type. + try: + annotated_type = self.type_hints_with_extras[param.name] + except KeyError: + annotated_type = param.annotation + + return Parameter( + name=get_alt_name(annotated_type) or normalize_name(param.name), + signature=param, + resolved_type=annotation, + is_nullable=is_nullable(TypeHint(annotation)), + doc=get_doc(annotated_type), + ignore=get_ignore(annotated_type), + default_path=get_default_path(annotated_type), + default_address=get_default_address(annotated_type), + deprecated=get_deprecated(annotated_type), + 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: + logger.exception("Unexpected type while binding input values to arguments") + raise InvalidInputError(str(e)) 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 + deprecated: str | None = None + 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: + msg = f"No function '{name}' in {self}" + raise RegistrationError(msg) from None + + return fn.bind_parent(parent) + + def __str__(self): + s = "interface" if self.interface else "object" + return f"{s} '{self.cls.__module__}.{self.cls.__name__}'" diff --git a/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/_types.py b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/_types.py new file mode 100644 index 0000000..8e6e771 --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/_types.py @@ -0,0 +1,58 @@ +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 + deprecated: str | None = None + + +@dataclasses.dataclass(slots=True, frozen=True) +class FunctionDefinition: + name: APIName | None = None + doc: str | None = None + cache: str | None = None + deprecated: str | None = None + check: bool = False + generator: bool = False + service: bool = False + agent: bool = False + + +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/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/_utils.py b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/_utils.py new file mode 100644 index 0000000..6149876 --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/_utils.py @@ -0,0 +1,368 @@ +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 DefaultAddress, DefaultPath, Deprecated, 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 + + +@dataclasses.dataclass(slots=True) +class EnumMemberDoc: + description: str | None = None + deprecated: str | None = None + + +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_default_address(obj: Any) -> str | None: + """Get the last DefaultAddress() of an annotated type.""" + meta = get_meta(obj, DefaultAddress) + return meta.address 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 get_deprecated(obj: Any) -> str | None: + """Get the deprecation metadata from an annotated type.""" + if meta := get_meta(obj, Deprecated): + return meta.reason + return 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 _parse_enum_docstring(text: str) -> EnumMemberDoc: + description_lines: list[str] = [] + deprecated_lines: list[str] = [] + lines = text.splitlines() + it = iter(enumerate(lines)) + for _, raw_line in it: + stripped = raw_line.strip() + if stripped.startswith(".. deprecated::"): + # capture first line after the directive + remainder = stripped[len(".. deprecated::") :].strip() + if remainder: + deprecated_lines.append(remainder) + # grab any indented continuation lines + for _, cont in it: + cont_stripped = cont.strip() + if not cont_stripped: + continue + if cont.startswith((" ", "\t")): + deprecated_lines.append(cont_stripped) + continue + # hit a non-indented line: feed it back into the outer loop + description_lines.append(cont_stripped) + break + else: + description_lines.append(stripped) + description = "\n".join(line for line in description_lines if line).strip() + deprecated = "\n".join(line for line in deprecated_lines if line).strip() + return EnumMemberDoc( + description=description or None, + deprecated=deprecated or None, + ) + + +def extract_enum_member_doc(cls: type[enum.Enum]) -> dict[str, EnumMemberDoc]: + """Extract docstrings for enum members by parsing the AST.""" + member_docs: dict[str, EnumMemberDoc] = {} + + 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] = _parse_enum_docstring(doc) + + return member_docs diff --git a/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/cli.py b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/cli.py new file mode 100644 index 0000000..86d0866 --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/mod/cli.py @@ -0,0 +1,104 @@ +"""Command line interface for the dagger extension runtime.""" + +import importlib +import importlib.metadata +import importlib.util +import logging +import os +import typing + +import anyio + +import dagger +from dagger import telemetry +from dagger.mod._exceptions import ModuleError, ModuleLoadError, record_exception +from dagger.mod._module import MAIN_OBJECT, Module + +logger = logging.getLogger(__package__) + +ENTRY_POINT_NAME: typing.Final[str] = "main_object" +ENTRY_POINT_GROUP: typing.Final[str] = typing.cast(str, __package__) +IMPORT_PKG: typing.Final[str] = os.getenv("DAGGER_DEFAULT_PYTHON_PACKAGE", "main") + + +def app(mod: Module | None = None, register: bool = False) -> int | None: + """Entrypoint for a Python Dagger module.""" + telemetry.initialize() + try: + return anyio.run(main, mod, register) + finally: + telemetry.shutdown() + + +async def main(mod: Module | None = None, register: bool = False) -> int | None: + """Async entrypoint for a Dagger module.""" + # Establishing connection early on to allow returning dag.error(). + # Note: if there's a connection error dag.error() won't be sent but + # should be logged and the traceback shown on the function's stderr output. + async with await dagger.connect(): + try: + if mod is None: + mod = load_module() + if register: + return await mod.register() + return await mod.serve() + except (ModuleError, dagger.QueryError) as e: + await record_exception(e) + return 2 + except Exception as e: + logger.exception("Unhandled exception") + await record_exception(e) + return 1 + + +def load_module() -> Module: + """Load the dagger.Module instance via the main object entry point.""" + ep = get_entry_point() + try: + cls = ep.load() + except Exception as e: + logger.exception( + "Error while importing Python module '%s' with Dagger functions", + ep.module, + ) + raise ModuleLoadError(str(e)) from e + try: + return cls.__dagger_module__ + except AttributeError: + msg = ( + "The main object must be a class decorated with @dagger.object_type, " + f"found '{type(cls)}'" + ) + raise ModuleLoadError(msg) from None + + +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" + + if not importlib.util.find_spec(import_pkg): + 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 ModuleLoadError(msg) + + return importlib.metadata.EntryPoint( + group=ENTRY_POINT_GROUP, + name=ENTRY_POINT_NAME, + value=f"{import_pkg}:{MAIN_OBJECT}", + ) diff --git a/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/py.typed b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/telemetry.py b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/telemetry.py new file mode 100644 index 0000000..992ca9a --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/sdk/src/dagger/telemetry.py @@ -0,0 +1,212 @@ +import logging +import os +from typing import Final + +from opentelemetry import context, propagate, trace +from opentelemetry.environment_variables import OTEL_PYTHON_TRACER_PROVIDER +from opentelemetry.instrumentation.logging import LoggingInstrumentor +from opentelemetry.instrumentation.logging.environment_variables import ( + OTEL_PYTHON_LOG_CORRELATION, + OTEL_PYTHON_LOG_FORMAT, + OTEL_PYTHON_LOG_LEVEL, +) +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, +) +from opentelemetry.sdk.environment_variables import ( + OTEL_EXPORTER_OTLP_ENDPOINT, + OTEL_EXPORTER_OTLP_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.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(*, debug: bool = False): + """Configure telemetry. + + If debug is True, enables console exporters. + """ + _DaggerPropagationConfigurator().configure() + _DaggerOtelConfigurator().configure(debug=debug) + + +def get_tracer() -> trace.Tracer: + """Returns a tracer to use with Dagger.""" + initialize() + return trace.get_tracer("dagger.io/sdk.python") + + +def shutdown(): + """Process all spans that have not yet been processed.""" + # TODO: set a timeout + + tracer_provider = get_tracer_provider() + # Provider shutdown is called automatically on exit, we just need the forced + # flush but might as well shutdown now too. + + if isinstance(tracer_provider, sdktrace.TracerProvider): + tracer_provider.force_flush() + tracer_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): + exporters = ("otlp",) + + # 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 + + if kwargs.get("debug"): + self.exporters += ("console",) + + 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") + + # Logging instrumentation. + os.environ.setdefault(OTEL_PYTHON_LOG_CORRELATION, "true") + os.environ.setdefault(OTEL_PYTHON_LOG_LEVEL, "warning") + os.environ.setdefault( + OTEL_PYTHON_LOG_FORMAT, + "%(levelname)s [%(name)s]: %(message)s", + ) + + _vars = { + OTEL_EXPORTER_OTLP_ENDPOINT: OTEL_EXPORTER_OTLP_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: Dagger's engine only accepts Gauge metrics today (engine-side + # exec resource monitoring). Emitting counters/histograms from modules + # produces 500s and retry noise, so skip metric initialization by + # passing an empty list for the metrics slot of _import_exporters. + # + # Also skip OTel log exporting. Dagger already captures module + # stdout/stderr and forwards it as logs; installing OTel's logging + # handler would duplicate Python logging records in the TUI. + trace_exporters, _, _ = _import_exporters( + _get_exporter_names("traces"), + [], + [], + ) + logger.debug( + "Initializing traces telemetry with exporters: %s", + ", ".join(trace_exporters) if trace_exporters else "none", + ) + _init_tracing(trace_exporters) + + # The logging instrumentor injects the trace context into logs without + # exporting them separately. + LoggingInstrumentor().instrument() diff --git a/.dagger/modules/e2e/fixtures/runtime/app/src/runtime_app/__init__.py b/.dagger/modules/e2e/fixtures/runtime/app/src/runtime_app/__init__.py new file mode 100644 index 0000000..621588d --- /dev/null +++ b/.dagger/modules/e2e/fixtures/runtime/app/src/runtime_app/__init__.py @@ -0,0 +1,8 @@ +from dagger import function, object_type + + +@object_type +class RuntimeApp: + @function + def greeting(self) -> str: + return "served by the python-sdk runtime" diff --git a/.dagger/modules/e2e/fixtures/toml-generate/app/dagger-module.toml b/.dagger/modules/e2e/fixtures/toml-generate/app/dagger-module.toml new file mode 100644 index 0000000..662951b --- /dev/null +++ b/.dagger/modules/e2e/fixtures/toml-generate/app/dagger-module.toml @@ -0,0 +1,5 @@ +name = "toml-generate-app" +engineVersion = "v1.0.0-0" + +[runtime] +source = "python" diff --git a/.dagger/modules/e2e/fixtures/toml-generate/app/pyproject.toml b/.dagger/modules/e2e/fixtures/toml-generate/app/pyproject.toml new file mode 100644 index 0000000..dbd8550 --- /dev/null +++ b/.dagger/modules/e2e/fixtures/toml-generate/app/pyproject.toml @@ -0,0 +1,12 @@ +[project] +name = "toml-generate-app" +version = "0.1.0" +requires-python = ">=3.14" +dependencies = ["dagger-io"] + +[build-system] +requires = ["uv_build>=0.8.4,<0.12.0"] +build-backend = "uv_build" + +[tool.uv.sources] +dagger-io = { path = "sdk", editable = true } diff --git a/.dagger/modules/e2e/fixtures/toml-generate/app/src/toml_generate_app/__init__.py b/.dagger/modules/e2e/fixtures/toml-generate/app/src/toml_generate_app/__init__.py new file mode 100644 index 0000000..d05f62e --- /dev/null +++ b/.dagger/modules/e2e/fixtures/toml-generate/app/src/toml_generate_app/__init__.py @@ -0,0 +1,8 @@ +from dagger import function, object_type + + +@object_type +class TomlGenerateApp: + @function + def hello(self) -> str: + return "hello" diff --git a/.dagger/modules/e2e/main.dang b/.dagger/modules/e2e/main.dang index 95069ff..3e27eab 100644 --- a/.dagger/modules/e2e/main.dang +++ b/.dagger/modules/e2e/main.dang @@ -14,6 +14,9 @@ type E2e { let configModulePath: String! = fixtureRoot + "/config/app" let configuredModulePath: String! = fixtureRoot + "/config/configured" let tomlModulePath: String! = fixtureRoot + "/toml/app" + let runtimeModulePath: String! = fixtureRoot + "/runtime/app" + let tomlGenerateModulePath: String! = fixtureRoot + "/toml-generate/app" + let runtimeGreeting: String! = "served by the python-sdk runtime" let mixedDiscoveryModulePath: String! = fixtureRoot + "/mixed-discovery/ancestor/work/app" let mixedDiscoveryNestedPath: String! = mixedDiscoveryModulePath + "/nested/deeper" @@ -294,6 +297,43 @@ type E2e { null } + """ + A dagger-module.toml module is generated by this SDK's own code generator, + not by the runtime its config names. + + The tell is what gets vendored: this SDK vendors only the client library, + while the engine's builtin passes its whole sdk/python tree and so drops a + copy of its runtime into the module as well. + """ + pub tomlGenerateCheck(ws: Workspace!): Void @check { + let changes = pythonSdk.mod(ws, path: tomlGenerateModulePath).generate + let genPath = tomlGenerateModulePath + "/" + generatedMarkerPath + + assertAdded(changes, genPath) + assertContains(changes.layer.file(genPath).contents, generatedMarkerContents, "generate did not produce code-generated bindings") + assert( + contains(changes.addedPaths, tomlGenerateModulePath + "/sdk/runtime/dagger.json") == false, + "the module was generated by the engine's builtin Python SDK, not this one", + ) + + # A module needs the importable library and nothing else. These are the + # things that have been vendored at some point and should not come back: + # the code generator runs in this SDK, the library's lock pins the + # generator's environment, and provisioning starts an engine a module + # already has. + assertNoneAdded( + changes, + [ + tomlGenerateModulePath + "/sdk/codegen/pyproject.toml", + tomlGenerateModulePath + "/sdk/uv.lock", + tomlGenerateModulePath + "/sdk/src/dagger/provisioning/__init__.py", + ], + "generate", + ) + + null + } + """ Generating all modules should discover Python SDK modules and ignore skipped ones. """ @@ -324,6 +364,10 @@ type E2e { """ The SDK should report the builtin runtime that modules it authors target. + + Still the builtin: modules created by this SDK move to this repository's own + runtime only once `runtime/` exists on the default branch, since the ref they + would record resolves from there rather than from a pull request. """ pub targetRuntimeCheck: Void @check { assert(pythonSdk.targetRuntime == "python", "targetRuntime should be the python builtin runtime") @@ -331,6 +375,109 @@ type E2e { null } + """ + A module whose runtime is this repository's own should build and run. + + Driven through a released CLI so nothing about the call is mocked: the + fixture points its `[runtime] source` at `runtime/` by relative path, so + what answers is the runtime in this working tree, not a published one. + """ + pub runtimeCallCheck(ws: Workspace!): Void @check { + let run = sdkSdk + .target(ws.directory("/"), ".") + .runInstalled(["call", "-m", runtimeFixturePath, "greeting"]) + run.assertSuccess + assertContains(run.stdout, runtimeGreeting, "the module did not run on this repository's runtime") + + null + } + + """ + The runtime must refuse a module whose generated files are missing rather + than quietly regenerating them. + + This is the no-codegen-at-module-load contract. The engine's builtin runtime + honours it too, so this is not a behaviour difference — it is proof that this + implementation of it works. + """ + pub runtimeRequiresGeneratedFilesCheck(ws: Workspace!): Void @check { + let stripped = ws + .directory("/") + .withoutFile(runtimeModulePath + "/sdk/pyproject.toml") + let run = sdkSdk + .target(stripped, ".") + .runInstalled(["call", "-m", runtimeFixturePath, "greeting"]) + run.assertFailure + assertContains(run.stderr, "run `dagger generate` and commit", "the runtime did not report the missing generated file") + + null + } + + """ + Path of the runtime fixture inside the sdk-sdk harness's scratch workspace, + where this repository is vendored rather than sitting at the root. + """ + let runtimeFixturePath: String! = "vendor/sdk-workspace/" + fixtureRoot + "/runtime/app" + + """ + The runtime module's own directory. + """ + let runtimeSource(ws: Workspace!): Directory! { + ws + .directory("/", include: ["runtime/**", "!runtime/**/__pycache__"]) + .directory("runtime") + } + + """ + This SDK's vendored client library and code generator. + """ + let sdkSource(ws: Workspace!): Directory! { + ws + .directory("/", include: ["sdk/**", "!sdk/**/.venv", "!sdk/**/__pycache__"]) + .directory("sdk") + } + + """ + The runtime's Go unit tests should pass. + + This also compiles the committed generated bindings, which is the only place + a mismatch between them and the runtime's source would show up before a + module tries to load. + """ + pub runtimeGoTestCheck(ws: Workspace!): Void @check { + container + .from("golang:1.26-alpine") + .withoutEntrypoint + .withMountedCache("/go/pkg/mod", cacheVolume("go-mod")) + .withMountedCache("/root/.cache/go-build", cacheVolume("go-build")) + .withDirectory("/runtime", runtimeSource(ws)) + .withWorkdir("/runtime") + .withExec(["go", "test", "./..."], experimentalPrivilegedNesting: true) + .sync + + null + } + + """ + The vendored client library and code generator should pass their own tests. + + These are the upstream suites for exactly what this repository now owns: + generator output and module registration. They are what catches the vendored + copy breaking as it diverges from dagger/dagger's. + """ + pub sdkTestCheck(ws: Workspace!): Void @check { + container + .from("ghcr.io/astral-sh/uv:python3.14-alpine") + .withoutEntrypoint + .withMountedCache("/root/.cache/uv", cacheVolume("python-sdk-uv")) + .withDirectory("/sdk", sdkSource(ws)) + .withWorkdir("/sdk") + .withExec(["uv", "run", "--frozen", "pytest", "-q", "tests/codegen", "tests/mod"], experimentalPrivilegedNesting: true) + .sync + + null + } + """ config.get should reflect pyproject.toml and report unset values as null rather than guessing, and config.set should edit only pyproject.toml. diff --git a/README.md b/README.md index 2fc064d..132a9d4 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,67 @@ required engine version — are owned by the core CLI (`dagger module deps`, It uses the engine's native `Workspace` and `ModuleSource` APIs directly. +## What lives here + +| Path | What it is | +| --- | --- | +| `python-sdk.dang`, `mod.dang`, `templates/` | authoring: `initModule`, `generate`, config, discovery | +| `sdk/` | the `dagger-io` client library and code generator | +| `runtime/` | the module runtime the engine calls to run a module | + +Code generation happens at `dagger generate`, through this module's `@generate` +hook, which runs the code generator in `sdk/` and vendors the result into the +module. The runtime never generates: it builds a module from its **committed** +generated files, so there is no codegen step in a cold `dagger call`, and a +module that has not been generated fails with an actionable error rather than +being silently regenerated. + +Pre-1.0 `dagger.json` modules are the exception: they keep being generated and +run by the Python SDK baked into the engine, exactly as before. + +## Two runtimes, one name + +Python modules reach one of two implementations, and which one is decided by +the module's config format: + +- **Legacy** — a `dagger.json` with `"sdk": {"source": "python"}` resolves to + the runtime baked into the engine (`dagger/dagger`'s `sdk/python`), which + still generates bindings at module load. Nothing about those modules changes, + and they need no migration. +- **Modern** — a `dagger-module.toml` can point `[runtime] source` at this + repository's `runtime/`, which is the no-codegen path above. Either a module + ref or a path relative to the module works, for both `dagger generate` and + `dagger call`. + +The engine resolves the short name `python` to exactly one target, the +engine-baked runtime, so the modern path is reached by module ref rather than +by name. `targetRuntime` — what `dagger module init python` writes into a new +module — is therefore still `python` today; it moves to +`github.com/dagger/python-sdk/runtime` in a follow-up, once `runtime/` exists +on the default branch for that ref to resolve to. See +[`future/done/self-contained-python-sdk.md`](./future/done/self-contained-python-sdk.md) +for the full reasoning and for the engine change that would let one name serve +both. + +### Trying this repository's runtime + +`targetRuntime` still writes `python`, so a module created today runs on the +engine's runtime. To move one onto this repository's runtime, point it there by +hand: + +```toml +# /dagger-module.toml +[runtime] +source = "github.com/dagger/python-sdk/runtime" +``` + +Then `dagger generate` the module and `dagger call` it as usual. The generated +files are identical either way — generation is this SDK's regardless of which +runtime runs the module — so switching back is just editing the line again. + +Within this repository, a path relative to the module works too, which is how +the end-to-end fixture exercises the runtime before the ref exists. + ## Install From your workspace root: diff --git a/dagger.json b/dagger.json index 5bcfdef..21c4c4d 100644 --- a/dagger.json +++ b/dagger.json @@ -3,5 +3,11 @@ "engineVersion": "v1.0.0-beta.10", "sdk": { "source": "dang" - } + }, + "include": [ + "!runtime", + "!.dagger", + "!future", + "!docs" + ] } diff --git a/dagger.lock b/dagger.lock index 13afe35..21310db 100644 --- a/dagger.lock +++ b/dagger.lock @@ -2,4 +2,6 @@ ["","container.from",["docker.io/library/alpine:3.22","linux/amd64"],"sha256:14358309a308569c32bdc37e2e0e9694be33a9d99e68afb0f5ff33cc1f695dce"] ["","container.from",["docker.io/library/golang:1.25-alpine","linux/amd64"],"sha256:1ae0735f00daffa3aaf1363a5184c0d2dc55c78e3db4ec70241cdac97bf84b59"] ["","container.from",["docker.io/library/golang:1.26-alpine","linux/amd64"],"sha256:28d89ee9cc0ff9fec75c82ca201e6bf7fdf9a679d4b7b24dfa04f2bb766bb468"] +["","container.from",["ghcr.io/astral-sh/uv:python3.14-alpine","linux/amd64"],"sha256:56b2e7ad659cd4b8d3abb5e67556a1e7ac53adf3a13eb8c292696df9c1e70b67"] +["","git.ref",["https://github.com/dagger/go-sdk","HEAD"],{"ref":"refs/heads/main","sha":"902440e3e097a42cf16354e5eba6b2f0b86194e3"}] ["","git.ref",["https://github.com/dagger/sdk-sdk","HEAD"],{"ref":"refs/heads/main","sha":"334448911a8292fba0d677e5f31926c79ad80ad3"}] \ No newline at end of file diff --git a/dagger.toml b/dagger.toml index 7036eb5..01998c7 100644 --- a/dagger.toml +++ b/dagger.toml @@ -16,6 +16,15 @@ check.skip = ["*"] [modules.sdk-sdk] source = "github.com/dagger/sdk-sdk" +[modules.go-sdk] +source = "github.com/dagger/go-sdk" + +[modules.go-sdk.as-sdk] +name = "go" + +[[modules.go-sdk.as-sdk.modules]] +path = "runtime" + [modules.python-sdk.as-sdk] name = "python" @@ -42,3 +51,6 @@ path = ".dagger/modules/e2e/fixtures/config/app" [[modules.python-sdk.as-sdk.modules]] path = ".dagger/modules/e2e/fixtures/config/configured" + +[[modules.python-sdk.as-sdk.modules]] +path = ".dagger/modules/e2e/fixtures/toml-generate/app" diff --git a/future/done/self-contained-python-sdk.md b/future/done/self-contained-python-sdk.md new file mode 100644 index 0000000..aba0753 --- /dev/null +++ b/future/done/self-contained-python-sdk.md @@ -0,0 +1,651 @@ +# Self-contained Python SDK + +author: yves +created: 2026-08-17 +status: done (PR 1 of 2 landed as a draft; PR 2 flips targetRuntime) +related: `github.com/dagger/dagger` `future/spin-out-generated-clients.md`, +`future/sdk-tests.md`; `github.com/dagger/java-sdk` (prior art) + +## Problem + +`dagger/python-sdk` is not the Python SDK. It is only the *authoring* half of +it: `initModule`, `mod … config`, `generateAll`, module discovery. Everything +that actually makes a Python module run — the client library (`src/dagger`), +the code generator (`codegen/`), and the module runtime — still lives in +`dagger/dagger` under `sdk/python/`, is baked into the engine container, and is +resolved by a pinned digest. + +Verified on `dagger/dagger@501b57e0476dee5881b99a064c3c04173134ecc7` +(2026-08-14), and, where the design depends on the *released* engine the checks +run against, also on `v1.0.0-beta.9`: + +- `core/sdk/loader.go:141` → `namedSDK`: `case sdkPython: return + l.loadBuiltinSDK(ctx, root, sdk, digest.Digest(os.Getenv(distconsts.PythonSDKManifestDigestEnvName)))`. + `namedSDK` is tried before any ref and only `errUnknownBuiltinSDK` falls + through (`core/sdk/loader.go:56-61`), and `python@x` is rejected outright + (`:276-278`). The short runtime name `python` has exactly one resolution + target: the engine-baked `sdk/python/runtime` module. +- The workspace `[modules..as-sdk]` registry is an **authoring** registry. + `installedSDKSource`'s only callers are `workspace_module_init.go`, + `workspace_builders.go`, `workspace_client.go` and `workspace_sdk.go`; module + loading explicitly does not use it — "the runtime itself resolves in-engine + when a consuming module loads" (`engine/server/session_workspaces.go:539-546`). +- `core/sdk/workspace_module.go` → `WorkspaceModuleForRuntime` has no non-test + callers. Its static name→ref table is a superseded pattern; the CLI's + `internal/cmd/dagger/sdks.json` + `setupResolveMigratedSDKs` own migration + name resolution now. It must not be revived as a module-loading hook. + +The consequence that matters is **release-cadence ownership**, not behaviour: +the Python SDK cannot ship a runtime, library or codegen fix without an engine +release, and this repo's checks can only exercise the parts of the SDK it +actually contains. + +(Two things this doc previously overstated, corrected: the e2e suite does +already execute the builtin runtime's codegen in a container via +`e2e:generate-check`; and modern modules already avoid runtime codegen today — +`v1.0.0-beta.9:sdk/python/runtime/main.go:195` has `moduleRuntimeTrusted`. So +goal 2 below is about *where the code lives and who releases it*, plus a +genuinely simpler implementation — not a new user-visible capability.) + +## Goals + +1. `dagger/python-sdk` contains the Python client library, the code generator, + and a module runtime — enough to make a Python module work end to end, + released on this repo's cadence. +2. New and migrated (`dagger-module.toml`) modules run on **python-sdk's own** + runtime, which is no-codegen-at-runtime and materially simpler than the + current combined runtime. +3. Legacy (`dagger.json`) modules keep working **exactly** as today, served by + `dagger/dagger`'s in-tree `sdk/python` codegen + runtime, with codegen at + runtime. No behaviour change, no migration required. +4. Keep the runtime name `python` wherever the engine allows it, and document + precisely where it does not and what would change that. + +## Non-goals (YAGNI) + +- Deleting or modifying `dagger/dagger`'s `sdk/python`. Legacy modules depend + on it; it stays as-is. This work is additive from `dagger/dagger`'s side. +- Any change to `dagger/dagger` in this workstream. See *Residual engine work*. +- Publishing `dagger-io` to PyPI from this repo, or changing the released + library's packaging/versioning story. +- Porting `sdk/python`'s Sphinx docs, changelog history, or its client/ + provisioning test suites. +- Supporting `dagger init --sdk python` / `dagger develop` scaffolding from the + new runtime. Template scaffolding is `initModule`'s job in this repo. +- A `pip`-only (non-`uv`) code path beyond what the copied code already + provides. Lock-file semantics are copied verbatim, not redesigned. + +## Verified constraints + +The engine decides whether a runtime may regenerate bindings **by config file +format alone**, independent of which SDK is loaded (`core/sdk/utils.go:20-25`, +same at `v1.0.0-beta.9`): + +```go +// dagger.json always (legacy behavior), dagger-module.toml never +func useRuntimeCodegen(src dagql.ObjectResult[*core.ModuleSource]) bool { + return src.Self().ConfigFilename != modules.Filename // "dagger-module.toml" +} +``` + +and it omits the introspection JSON from the `moduleRuntime` call when that is +false *and* the SDK opted in by declaring `introspectionJson` optional +(`core/sdk/module_runtime.go:50,90-91`, `core/sdk/module.go:75-88`). + +So the legacy/modern split is expressed for us by the engine, at the +`moduleRuntime` call boundary. We do not sniff for `dagger.json` in the runtime. + +Precisely: a module whose config is `dagger-module.toml` is never handed +introspection JSON. This is *not* the same as "the new runtime can never +receive it" — nothing stops a legacy `dagger.json` from naming +`github.com/dagger/python-sdk/runtime` explicitly, in which case introspection +*is* passed and ignored. That module then fails `requireGeneratedFiles` with an +actionable "run `dagger generate` and commit" error unless it has committed +them, in which case it simply works. Either way this is a +supported-configuration statement, not an engine-enforced invariant. + +`codegen` is different: it is always called with introspection JSON +(`core/sdk/module_code_generator.go:36,50-61`). That is authoring time, not +module-load time, and it is where the copied code generator has to live. + +## Proposed approach + +Two additions to this repo, no removals from `dagger/dagger`. + +### 1. `runtime/` — python-sdk's own module runtime + +A copy of `dagger/dagger:sdk/python/runtime` (a Go Dagger module), then +simplified, renamed to `python-sdk-runtime` so it does not collide with this +repo's root module name. + +### 2. `runtime/sdk/` — the vendored client library and code generator + +A copy of `dagger/dagger:sdk/python`'s library surface (`pyproject.toml`, +`uv.lock`, `ruff.toml`, `LICENSE`, `README.md`, `src/dagger/**`, `codegen/**`, +`tests/conftest.py`, `tests/codegen/**`, `tests/mod/**`). This is what gets +vendored into modules and what runs codegen. + +**It lives inside the runtime module, not at the repo root, and is read with +`dag.CurrentModule().Source().Directory("sdk")`.** That is a deliberate +correction to the obvious layout. Upstream's `New()` takes the library through +a contextual argument (`+defaultPath=".."`), which is dead code for the builtin +path — the engine passes the directory explicitly +(`core/sdk/loader.go:212-252` → `core/sdk/module.go:114-122`) — and is *unsafe* +for a ref-loaded SDK: when a Workspace is bound into the context, contextual +argument resolution is redirected to the **consuming** workspace, "unilaterally, +whether the module was loaded from Host, Git, or a Directory" +(`core/modulesource.go:1461-1472`, `workspaceContextDirPath` at `:1541-1547`), +and `dagger generate` / `dagger check` bind one. A `+defaultPath="../sdk"` +would then resolve to `/sdk` in the *user's* workspace. + +`dag.CurrentModule().Source()` has no such ambiguity: `currentModuleSource` +(`core/schema/module.go:2951-3003`) builds from the module's own +`Source.ContextDirectory` and never consults `WorkspaceFromContext`. The +runtime already relies on it for its entrypoint script (`main.go:438-447`). +`sdkSourceDir` stays as an optional constructor argument so extension SDKs can +still inject their own, defaulting to the vendored copy when absent — which is +exactly the nil the engine passes for a ref-loaded module +(`core/sdk/loader.go:123` → `core/sdk/module.go:114-122`). + +**Dropping `+defaultPath` also drops `+ignore`, and that was load-bearing.** +Upstream's contextual argument carries an allowlist (`main.go:52`) applied as +`CopyFilter{Exclude: arg.Ignore}` (`core/modfunc.go:1069-1071`), which is what +keeps `WithSDK` from vendoring junk into every user module. A bare +`CurrentModule().Source().Directory("sdk")` is unfiltered, so the same +allowlist must be re-applied explicitly with `Directory.Filter`, and +`runtime/dagger.json` gets an `include` list as a second line of defence. This +matters most for the local-path fixture, whose runtime source comes from the +working tree: without it, a developer's `runtime/sdk/.venv` or `__pycache__` +would leak into both the module-source digest and the vendored output, and +nothing would fail loudly. + +### 3. What actually gets simplified + +The runtime is reached only by modules that build from committed files, so the +branch disappears rather than being carried: + +| | Today (dagger/dagger) | Here | +|---|---|---| +| `moduleRuntime` | branches on `introspectionJson == nil`: trusted path *or* vendor + codegen + template + lock-update + install | the trusted path, unconditionally | +| `TrustedSource` field | set only on the trusted path; gates two behaviours | gone as a *field*; both behaviours become unconditional (see below) | +| `Codegen` | vendor + codegen + template + lock-update | vendor + codegen + lock-update (authoring time) | +| `WithTemplate`, `template/{__init__,main,pyproject}` | scaffolds a new module | gone — `initModule` in this repo owns templates | +| `template/runtime.py` | shipped under `template/` | `runtime/runtime.py` — it is the entrypoint, not a template | + +Two corrections to an earlier, wrong version of this table, both found in +review: + +- **`TrustedSource` is load-bearing, not bookkeeping.** It gates keeping the + committed `sdk/` instead of stripping and re-vendoring it + (`discovery.go:292-305`) and adding `--locked` to `uv sync` + (`main.go:565-572`). "Always true" therefore means two deliberate + unconditional rewrites, not deleting a field and its `if`s. +- **`IsInit` and `MainObjectName` stay.** The trusted path reads `IsInit` to + raise its "no source to trust" error (`main.go:221-223`) and + `UseUvLock()` reads it (`discovery.go:153`); `MainObjectName` is exported as + `DAGGER_MAIN_OBJECT` by `WithSource` (`main.go:517`), which the trusted path + calls. Only the *template substitution* that used them goes away. + +Lock-file selection semantics (`UseUvLock`, `WithUpdates`, the +`requirements.lock` fallback) are copied unchanged. Modules scaffolded by this +repo's templates ship no lock file and, since `IsInit` is false for a +`dagger-module.toml` module, take the pip-compatible install path — exactly as +they do today under the engine builtin. Changing that is a separate decision, +deliberately not bundled here. + +### 4. `targetRuntime` points at this repo's runtime + +`python-sdk.dang`'s `targetRuntime` changes from `"python"` to +`"github.com/dagger/python-sdk/runtime"`, so `dagger module init python` +writes that into the new module's `dagger-module.toml` +(`core/schema/workspace_module_init.go:119-128` writes it verbatim). The engine +resolves it through `externalSDKForModule`. This is what `dagger/java-sdk` +already does (`main.dang:15`). + +This used to be **not a one-line change**: `mod()` decided whether a module +belonged to this SDK by pattern-matching its config file for `source = +"python"`, so every module this SDK created would have been rejected by its own +`mod` API. That guess is gone — see *Follow-up: module identity* below — and +changing `targetRuntime` no longer touches module identification at all. + +Legacy modules are untouched: their `dagger.json` keeps `sdk.source: "python"`, +which keeps resolving to the engine builtin. + +### Why not keep `targetRuntime = "python"` + +Because `namedSDK` matches the builtin table before it ever tries a ref, and +nothing between a `dagger-module.toml`'s `[runtime] source` and `SDKForModule` +consults the workspace. `"python"` cannot mean two things, and the one thing it +means is the engine-baked module. + +Goal 4 is met as far as this repo can meet it: `python` remains the name users +type (`dagger module init python`), the name in `sdks.json`, and the runtime +name for every legacy module. Only the value *written into a new module's* +`dagger-module.toml` differs. + +## Delivery sequencing — two PRs, and why + +This repo's CI runs `github.com/dagger/sdk-sdk`'s black-box checks, which +vendor the working tree, install it as a local path +(`sdk-target.dang:182-197,299-301`), scaffold a module with it, then run +`dagger generate` (`checks-generate.dang:8-12`) and `dagger api functions` +(`checks-module.dang:15-18`) through a **released** CLI +(`sdkSdk.daggerCliVersion = "1.0.0-beta.9"`). The SDK is local; the runtime ref +the scaffolded module records is not — it resolves from this repository's +`main` via `git.head`. + +So flipping `targetRuntime` in the same change that introduces `runtime/` would +point CI at a path that does not exist yet. Hence: + +- **PR 1 (this workstream)** — add `runtime/` (with `runtime/sdk/`), widen + module identification, and prove the runtime end to end against an in-repo + fixture that references it by *local path*. `targetRuntime` stays `"python"`. +- **PR 2 (immediately after PR 1 merges)** — flip `targetRuntime`, update + `e2e:target-runtime-check`. Green because `runtime/` is by then on `main`. + +**PR 1 is preparatory: no user-created module reaches the new runtime until PR +2.** That is a real property of the split and worth stating plainly rather than +dressing up. What makes PR 1 worth landing on its own is that the fixture +exercises the new runtime in CI on every subsequent commit — including PR 2, +which otherwise could not test the runtime it switches to, since +`sdk-sdk:module:loads` resolves the ref from `main` forever, not just once. + +## Residual engine work (out of scope, report only) + +Restoring a literal `runtime = "python"` for modern modules needs a +`dagger/dagger` change. An earlier draft called it "one line"; it is three +coordinated edits plus a policy decision: + +1. `core/sdk/loader.go` — move `sdkPython` out of `loadBuiltinSDK` into the + ref-resolving branch used by `sdkJava`/`sdkPHP`/`sdkElixir`. +2. `core/sdk/workspace_module.go:44-46` — repoint the table entry from + `github.com/dagger/python-sdk` (this repo's *authoring* module, which + implements no `moduleRuntime`) to `github.com/dagger/python-sdk/runtime`. + Java only works because `sdk/java/dagger.json` sets `"source": "runtime"`. +3. `core/sdk/loader.go:276-283` — decide python's versioning. `parseSDKName` + currently rejects `python@` and assigns no default, whereas + java/php/elixir default to `engine.Tag` with the commit fallback at + `:160-189`. This inherits dagger/dagger#13755. + +It would also route every legacy `dagger.json` module here, which this runtime +deliberately does not serve. Separate, explicitly-scoped decision — reported to +the Chief of Staff, not folded in. + +## Affected components (PR 1) + +- `future/done/self-contained-python-sdk.md` (this doc) +- `runtime/**` (new) — the simplified module runtime +- `runtime/sdk/**` (new) — vendored client library + code generator +- `python-sdk.dang` — `mod()` identifies modules by the workspace list +- `dagger.json` — `include` list, so the authoring module's source does not + grow by ~35k lines of vendored + generated code +- `.dagger/modules/e2e/main.dang` + `fixtures/runtime/**` — runtime e2e +- `README.md` — document the two paths + +`targetRuntime` is PR 2's change. + +## Testing + +**Runtime execution, via the sdk-sdk harness.** The existing e2e checks are +pure-Dang workspace assertions; Dang has no dynamic function invocation and no +way to catch a failed call, so "assert the call returns X" and "assert this +error text" are not expressible there. They *are* expressible through +`sdkSdk.target(view, sourceRootPath).run([...])` → +`SdkRun.assertSuccess`/`assertFailure`/`stderr` (`sdk-run.dang:25-62`), already +installed in `dagger.toml`. New checks: + +- `runtimeCall` — `dagger call` the fixture, assert the returned value. The + fixture is addressed as a one-off module in a single command: + `run(["call", "-m", "", ""])`; `-m/--load-module` accepts a + local path at beta.9 (`internal/cmd/dagger/module.go:40`), and the fixture's + six-`..` runtime path stays inside the harness's `git init`ed `/work`. +- `runtimeRequiresGeneratedFiles` — same fixture with its committed bindings + removed must `assertFailure` with the "run `dagger generate` and commit" + message, proving codegen really is gone from the runtime path rather than + silently regenerating. + +**Fixture.** `.dagger/modules/e2e/fixtures/runtime/app/` with +`[runtime] source = "../../../../../../runtime"` — six `..`, not five: the +fixture is six segments deep (`.dagger/modules/e2e/fixtures/runtime/app`). A +relative local path is a legal runtime source (`ResolveDepToSource`, +`core/modulesource.go:2016-2023`; the engine itself writes relative local refs +at `workspace_sdk.go:240-250`; dagger/dagger's own elixir testdata does it). +The fixture must commit the whole vendored `sdk/` because `requireGeneratedFiles` +demands `sdk/pyproject.toml` and `sdk/src/dagger/client/gen.py` +(`main.go:244-267`) — generated by running `initModule` + `dagger generate`, not +hand-written. It gets **no** skip-generate marker, since one of the checks +generates it (an earlier draft asked for both, which cancel out). + +**Legacy path regression net.** `e2e:generate-check` and +`e2e:generate-all-check` run against the existing `fixtures/generate/app` +(`dagger.json`, `sdk.source: "python"`), still served by the engine builtin. +Captured before the change and re-run after. + +**Codegen fidelity.** The earlier plan proposed byte-comparing the modern +fixture's `gen.py` against the legacy fixture's. That is invalid: output depends +on the module's dependency set via `SchemaIntrospectionJSONFileForModule`, and +`fixtures/generate/app` pins `engineVersion = "v0.20.8"`, which deliberately +selects a different codegen shape (`codegen/src/codegen/generator.py:368-374` +enables the legacy ID facade below v0.21). Instead: assert `runtime/sdk/codegen` +is tree-identical to `dagger/dagger@:sdk/python/codegen`, and carry over +`tests/codegen` + `tests/mod` from upstream (the only upstream tests covering +what this repo now owns) with a `dagger check` that runs them. + +**Go unit tests.** `runtime/python_test.go` is carried over and trimmed; a +check runs `go test ./...` in `runtime/`, otherwise it is untested tree weight. + +## Risks + +- **Contextual-argument resolution.** Mitigated by design (see approach §2) but + worth re-verifying empirically at implementation time: load the fixture + through `dagger generate` from a scratch workspace and assert the vendored + `gen.py` is correct, not silently sourced from the caller's workspace. +- **Unpinned runtime ref.** `targetRuntime` is written with no `@version` and + no pin (`workspace_module_init.go:347-355`; `sourceWithPin` is bypassed on + this branch), so after PR 2 every modern Python module resolves this repo's + default branch through `git.head` (`core/modulerefs.go:180-203`). The + runtime↔engine version coupling the builtin provided is gone; a module's + committed vendored SDK can drift from the runtime that loads it. Consuming + workspaces get a floating `dagger.lock` entry as partial mitigation. + java-sdk has the same exposure — precedent, not correctness. +- **Copy drift.** `runtime/sdk/` starts diverging from + `dagger/dagger:sdk/python` immediately. Mitigated by the provenance note and + the codegen tree-identity check; the divergence is the point, but the legacy + path depends on the `dagger/dagger` copy until the engine change lands. +- **Vendored library identity.** Modules vendor a `dagger-io` that is no longer + the PyPI-released one. The distribution name and version stay identical, and + the template's `[tool.uv.sources]` maps it to the vendored path, so there is + no new collision — but a divergent library under a released name is a real + hazard once it diverges. +- **Repo weight.** `runtime/sdk/src/dagger/client/gen.py` (~16.7k lines) plus + the runtime's committed Go bindings (~18.8k lines). Contained to `runtime/` + and kept out of the authoring module by `dagger.json`'s `include`. +- **The fixture's vendored SDK can go stale silently.** It was generated once + and committed; nothing compares it to `runtime/sdk`, so `runtime/sdk` can + change and the fixture keeps passing on its old copy. Closing the codegen gap + above (regenerating the fixture in CI) is what would fix this properly. +- ~~**`tomlConfigPattern`'s "durable fix" conflicts with the fixture.**~~ + Resolved: `mod()` now validates against the workspace list, and the runtime + fixture is reached by `dagger call -m ` rather than through `mod()`, so + it does not need to be a managed module. +- **Vendored client and provisioning code arrives without its tests.** + `tests/client` and `tests/provisioning` were not copied, so + `e2e:sdk-test-check` covers the code generator and module registration but + not the connection/session/provisioning code beneath them. +- **CI is Dagger Cloud checks, not GitHub Actions.** New coverage is + `dagger check` functions, and a runtime e2e is meaningfully slower than the + existing authoring checks. + +## Alternatives considered + +**Rewrite the runtime in Dang** (what `dagger/java-sdk` did). Attractive: the +rest of this repo is Dang, and java-sdk's runtime is 146 lines. But java-sdk's +runtime `codegen` is a deliberate no-op (`runtime/main.dang:51-53`) because its +*authoring* module owns generation (`mod.dang:66-72`), whereas this repo +delegates generation to the engine (`mod.dang:57-64`) — which is what forces +codegen into the runtime, and the runtime into a language that can drive it. +Rejected for PR 1 also because `discovery.go`'s package-name normalization and +Python-version selection are exactly where a re-implementation breaks modules +subtly. A Dang rewrite is a good follow-up once the copy is proven in CI, and +it would pair naturally with moving generation into the authoring module. + +**Teach the engine to route `python` here.** The only way to keep the literal +name. See *Residual engine work*. + +**Sniff for `dagger.json` inside the runtime.** Unnecessary: the engine already +makes that decision (`core/sdk/utils.go:20-25`). + +**One PR with a knowingly-red `sdk-sdk:module:loads`.** See *Delivery +sequencing*. + +## Implementation plan (PR 1) + +StGit patch series. Each patch carries `Signed-off-by: Yves Brissaud +`. + +1. **`future: design doc for a self-contained Python SDK`** *(done)* + +2. **`runtime: add the Python module runtime`** + Copy `dagger/dagger@501b57e04:sdk/python/runtime/**` → `runtime/**` + verbatim, then the minimum needed to make it live here: + - rename the module to `python-sdk-runtime` (`dagger.json`, `go.mod`, the + Go type, imports); + - keep the legacy `dagger.json` with `sdk.source: "go"`, so the Go SDK + regenerates `internal/dagger` at load rather than committing bindings + generated against a different engine. + Verbatim otherwise, so patch 4's diff *is* the simplification. + +3. **`runtime: vendor the Python client library and code generator`** + Copy `sdk/python/{pyproject.toml,uv.lock,ruff.toml,LICENSE,README.md, + .python-version,.gitattributes,.gitignore,src/**,codegen/**,tests/conftest.py, + tests/codegen/**,tests/mod/**}` → `runtime/sdk/` (`tests/conftest.py` is not + optional: it holds the only `anyio_backend` fixture, without which the ported + `tests/mod` cases error out). Provenance note naming the source commit. Trim + `pyproject.toml`'s `testpaths` / `source-include` to the trees actually + carried over. Rewire `New()`: `sdkSourceDir` becomes `+optional`, defaulting + to `dag.CurrentModule().Source().Directory("sdk")` with upstream's `+ignore` + allowlist re-applied via `Directory.Filter`; drop `+defaultPath`. Add an + `include` list to `runtime/dagger.json`. + +4. **`runtime: build modern modules from committed files only`** + The simplification, exactly as scoped in *What actually gets simplified* — + including the two corrections (keep `IsInit` / `MainObjectName`; make + `TrustedSource`'s two behaviours unconditional). Trim `python_test.go`. + +5. **`python-sdk: accept a module runtime other than the python builtin`** + Widen `tomlConfigPattern` so `mod()` validates modules whose `[runtime] + source` is `python`, this repo's runtime ref, or a local path. + +6. **`dagger: keep vendored code out of the authoring module's source`** + Add an `include` list to the root `dagger.json`. + +7. **`e2e: run a module through the new runtime`** + Fixture + `runtimeCall` / `runtimeRequiresGeneratedFiles` checks via the + sdk-sdk harness; a check running `go test ./...` in `runtime/`; a check + asserting `runtime/sdk/codegen` matches the pinned upstream tree. + +8. **`docs: describe the legacy and modern runtime paths`** + `README.md`: what lives where, which modules use which path, why + `targetRuntime` is still `"python"` today and what PR 2 changes. + +### Test strategy + +- `dagger check -l`, then targeted `dagger call e-2-e ` for each new + check against the dev CLI (`v1.0.0-beta.9`). +- Full `dagger check` before handoff, confirming the existing 35 checks are + untouched — `e-2-e:generate-check`, `e-2-e:generate-all-check` and + `sdk-sdk:module:loads` are the legacy path's regression net. + +## What implementation changed about the plan + +Three things only survived contact with a real engine in modified form. + +**1. A local-path runtime source resolves on load, but not through generate.** +`dagger call -m ` loads the fixture from the workspace (a *local* +module source, context = repo root) and `../../../../../../runtime` resolves — +verified, the module builds and returns its value. `dagger generate` on the same +module fails with `invalid SDK`. + +The cause is in the polyfill, not the engine. An earlier draft of this section +blamed `ResolveDepToSource`'s dir branch; that was wrong. `Workspace.moduleSource` +materializes the whole workspace tree +(`core/schema/workspace_module.go:140-159`), so `runtime/` *is* reachable. What +drops it is `dagger/polyfill`'s generate helper: at the pinned commit +(`16627066`) it builds a filtered view, +`workspace.Directory("/", Include: include).AsModuleSource(...)` +(`helpers/workspace-module-generate/main.go:213-220`), where `include` is +derived by `parseSourceConfigTOML` from `dependencies[].source` and `include` +only (`main.go:449-472`) — it never reads `[runtime] source`. A local-path +runtime is therefore filtered out of the view the module is generated from. + +Consequences, all confined to PR 1: + +- The fixture is **not** registered under `[[modules.python-sdk.as-sdk.modules]]`, + because `generateAll` would fail on it. +- Its vendored `sdk/` was generated once through the engine builtin and + committed, then verified by loading the module through *this* runtime. +- So PR 1 exercises this runtime's **module-load** path for real, but not the + code generation that then ran inside it. + +**Since superseded.** The runtime has no codegen at all any more: generation +moved to the SDK module's `@generate` hook, where `e2e:toml-generate-check` +covers it directly. + +This is a gap, not a law: adding `[runtime] source` to the include set that +polyfill's helper computes would close it, and is worth raising against +`dagger/polyfill`. It also disappears on its own in PR 2, where modules +reference the runtime by git ref rather than by path. + +**2. Codegen fidelity is checked by running tests, not by comparing trees.** +The plan called for asserting `runtime/sdk/codegen` is tree-identical to +upstream. That contradicts the design — divergence is the point of moving the +code here — and would have to be edited away on the first intentional change. +Replaced by running the vendored library's own suites (`tests/codegen`, +`tests/mod`, 169 tests) as `e2e:sdk-test-check`. + +**3. The runtime's Go tests need a Dagger session, and one of them was wrong.** +The generated client's `init()` panics without `DAGGER_SESSION_PORT`, so the +check runs `go test` with `experimentalPrivilegedNesting`. With the tests +actually running, `TestPackageNameNormalization` failed — and it fails upstream +too: it is byte-identical to `dagger/dagger`'s and feeds raw module names to +`NormalizePackageName`, which documents that it takes an already-normalized +project name and only maps `-` to `_`. Corrected to test the documented +contract plus the composed pipeline discovery actually uses. Production +behaviour is untouched; `discovery.go` already passes it a normalized name. + +## Follow-up: generation moved out of the runtime + +Landed after the first review round, on Yves's call, before merge. + +The runtime module implemented `codegen` because this SDK's `@generate` hook +did not generate anything itself: `generateAll` handed each module back to the +engine (`polyfill … moduleSource(path).generate` → +`GeneratedContextChangeset`), and the engine dispatches `codegen` to whatever +the module's `[runtime] source` names. So `dagger generate` on a Python module +ran the *engine's builtin* generator, and the code generator vendored here was +never reached — embedding it bought nothing. + +Now `generateAll`/`Mod.generate` generate directly for `dagger-module.toml` +modules: take the module's dependency schema, run `sdk/`'s code generator +against it, vendor the result. `dagger.json` modules keep going through the +engine, so the pre-1.0 path is untouched. + +Two things this depended on: + +- The schema must come from `ModuleSource.introspectionSchemaJSON` + (`core/schema/modulesource.go:257`), which loads only the *dependency* + modules. `Module.introspectionSchemaJSON` goes through `asModule`, which + builds the module's runtime — impossible before its bindings exist. +- A public Dang function cannot return a dependency's type, so the shared + fork helper stays private and `generateAll` merges `Changeset`s instead. + +Consequences: the runtime's `codegen` is a no-op (kept, because the engine +reads its presence as the SDK's code-generator capability), and everything that +served it is gone — SDK vendoring, `Common`/`WithSDK`/`WithUpdates`, +`SdkSourceDir` and its `dist/` probing, and `TrustedSource`, which now only ever +had one value. The client library moved from `runtime/sdk/` to `sdk/`, since the +authoring module is now its consumer and the runtime does not need it at all. + +This also closes the codegen coverage gap recorded above: `e2e:toml-generate-check` +generates a `dagger-module.toml` module and asserts the result came from this +SDK rather than the builtin, which vendors its whole `sdk/python` tree. + +## Follow-up: module identity + +Also landed before merge, on Yves's call. + +`mod()` used to pattern-match a module's config file to decide whether it +belonged to this SDK. That is a guess twice over: the text cannot distinguish +this SDK's runtime from any other module whose runtime path ends the same way, +and it is a second source of truth that can disagree with the engine's. + +The engine already owns the answer — `modules..as-sdk.modules` in +`dagger.toml`, reported through `currentModule.asSDK` — and `modules()` had +always used it. `mod()` now does too, so both agree by construction and neither +parses config text. `legacyConfigPattern`, `tomlConfigPattern`, +`validateConfig` and `configDir` are gone. + +The cost is that being managed is now what makes a module reachable through +`mod()`: the `config/app` and `config/configured` fixtures needed workspace +registrations. That is the intended contract for a 1.0 workspace, and the +README already said the engine owns that list. + +## Follow-up: rebased onto the polyfill removal + +`dagger/python-sdk#14` removed the polyfill for native workspace APIs, and this +branch now sits on top of it. Two things changed as a result: + +- Generation writes through the native workspace API instead of the polyfill + fork. That fixed a real bug: the fork resolved paths against a different root + than `vendorPath` assumed, so `dagger module init` wrote the vendored library + to a doubled path (`//sdk`). Verified by running + `dagger module init python` before and after. +- `Workspace.withNewDirectory` *replaces* the directory it writes, which would + delete anything a user had put under `sdk/`. Verified by A/B on a real + workspace: a stray file there survives generation when the new content is + layered onto the existing directory, and is deleted when it is not. + +**Since restructured.** `python-sdk: report only what generation produced` +(`c724b03`) reshaped generation again: it writes the generated context as a +directory overlay so `Workspace.changes` reports only the real delta. This +branch now plugs into that shape — for a `dagger-module.toml` module, `generate` +substitutes this SDK's vendored output for the engine's generated context, and +main's single layered `withNewDirectory` does the writing. So the +data-loss protection lives in one place, main's, rather than being duplicated +here. + +Neither behaviour has an e2e check: the destructive one only appears when a +changeset is applied to disk, and a check built on a workspace value passes +either way, so it would have proved nothing. The evidence is the on-disk A/B, +re-run after this rebase. + +## Progress + +- Phase 0 — orientation: done. + - Worktree: `…/python-sdk-runtime-consolidation-lead-ea131db2-e2b213c6` + - Branch `python-sdk-runtime-consolidation-lead-ea131db2`, base `main`, + remotes `origin=eunomie/python-sdk`, `upstream=dagger/python-sdk`. + - Design-doc home: `future/` (created; repo had none, and `future/` is the + convention in `dagger/dagger` and `dagger/go-sdk`). + - VCS: StGit patch stack. Sign-off: `Signed-off-by: Yves Brissaud + `. No AI attribution anywhere. + - Host: GitHub. CI: Dagger Cloud checks driven by `dagger.toml` (no + `.github/`). +- Phase 1/2 — feature doc + implementation plan: this document. +- Phases 6–8 — draft PR https://github.com/dagger/python-sdk/pull/17 at + `49cb500`, **39/39 CI checks green**, doc archived here. PR 2 (the + `targetRuntime` flip plus its check) is the remaining work, and is unblocked + now that `runtime/` is on the default branch. +- Phase 5 — code review: **passed**. Two independent reviewers on the diff, no + blockers. Fixes applied: the deviation-1 diagnosis was wrong and is corrected + above (polyfill's generate helper, not an engine limit — and therefore + fixable); `Codegen` now fails with an actionable error instead of letting + `uv lock` fail on a module with nothing to generate from; the missing-files + check no longer claims to be a behaviour difference from the builtin; the + runtime checks use the harness's `runInstalled` rather than paying for an + unrelated scaffold; a stale `WithoutDirectory("sdk/runtime")` that could have + deleted a user's directory is gone; vendored-tests and fixture-drift coverage + gaps are recorded in Risks; the two test checks moved out of the docs patch. +- Phase 4 — implemented. Seven patches; all 38 `dagger check` checks green + locally, including the two new runtime checks, the two new test checks, and + the legacy regression net (`e-2-e:generate-check`, `e-2-e:generate-all-check`, + `sdk-sdk:module:loads`). See *What implementation changed about the plan*. +- Phase 3 — adversarial plan review: **passed** after two rounds. + Round 2 independently verified the three load-bearing corrections + (`CurrentModule().Source()` is immune to the workspace redirect — + `core/schema/module.go:2951-3003`; `sdkSourceDir` really does arrive nil for a + ref-loaded SDK; `tomlConfigPattern` is the only behavioural hard-code of + `"python"`), and added four items now folded in: re-apply the `+ignore` + allowlist lost with `+defaultPath`, copy `tests/conftest.py`, address the + fixture with `dagger call -m `, and widen `tomlConfigPattern` narrowly + rather than to any local path. + Round 1 detail: a design/spec reviewer and a skeptic reviewed + independently. Both confirmed the central claims (the `python` name + cannot route here; relative local paths are legal runtime sources; the + two-PR constraint is real). Revisions folded in: the `+defaultPath` layout + was unsafe and became `runtime/sdk/` + `dag.CurrentModule().Source()`; + `tomlConfigPattern` is a blocking omission and moved into PR 1; `IsInit` / + `MainObjectName` / `TrustedSource` are load-bearing and stay; the engine + change is three edits, not one; the byte-identical codegen test was invalid + and was replaced; module rename, `dagger.json` include, unpinned-ref risk, + and the fixture path count all corrected. diff --git a/helpers/vendor-pyproject/strip_dev_sections.py b/helpers/vendor-pyproject/strip_dev_sections.py new file mode 100644 index 0000000..a1cb3e7 --- /dev/null +++ b/helpers/vendor-pyproject/strip_dev_sections.py @@ -0,0 +1,29 @@ +"""Strip a project file down to what a vendored library needs. + +As published, the Python SDK's pyproject.toml declares the code generator as a +uv workspace member and a dev dependency. A module vendors the library without +the generator, and uv refuses to install a project whose workspace member is +missing, so those sections are dropped on the way in. + +Usage: strip_dev_sections.py +""" + +import sys + +DROP = {"dependency-groups", "tool.uv.sources", "tool.uv.workspace"} + + +def main(src: str, dst: str) -> None: + kept: list[str] = [] + keep = True + for line in open(src): + stripped = line.strip() + if stripped.startswith("[") and stripped.endswith("]"): + keep = stripped.strip("[]") not in DROP + if keep: + kept.append(line) + open(dst, "w").write("".join(kept).rstrip() + "\n") + + +if __name__ == "__main__": + main(sys.argv[1], sys.argv[2]) diff --git a/mod.dang b/mod.dang index f32bc28..ce65f86 100644 --- a/mod.dang +++ b/mod.dang @@ -65,10 +65,19 @@ type Mod { let stagedWs = ws.withChanges( ws.moduleSource("/" + rootPath).generateLocalDependencies(ws), ) - let generated = stagedWs - .moduleSource("/" + rootPath) - .generatedContextDirectory - .directory(rootPath) + let generated = if (isModern) { + # A dagger-module.toml module is generated here, by this SDK's own code + # generator. Its runtime generates nothing, so asking the engine for a + # generated context would come back empty. + vendoredDir(stagedWs) + } else { + # A pre-1.0 module keeps being generated by the runtime its dagger.json + # names, which is the engine's builtin Python SDK. + stagedWs + .moduleSource("/" + rootPath) + .generatedContextDirectory + .directory(rootPath) + } # The generated context holds only generated files, and withNewDirectory # replaces rather than layers, so merge it onto the module first. @@ -80,4 +89,114 @@ type Mod { ).changes(ws) } } + + """ + Whether this module uses the 1.0 dagger-module.toml config. + """ + let isModern: Boolean! { + let configPath = if (rootPath == ".") { "dagger-module.toml" } else { rootPath + "/dagger-module.toml" } + ws.directory("/", include: [configPath]).exists(configPath) + } + + """ + This SDK's client library, with bindings generated against the module's own + schema, laid out the way it is vendored into a module. + + A module-rooted directory holding only `sdk/`, so the caller can merge it onto + the module without touching anything else the module owns. + """ + let vendoredDir(stagedWs: Workspace!): Directory! { + let schemaJSON = stagedWs.moduleSource("/" + rootPath).introspectionSchemaJSON + + directory.withDirectory( + vendorDirName, + library.withFile(generatedBindingsPath, bindings(schemaJSON)), + ) + } + + """ + Client bindings generated from a module's schema by this SDK's code generator. + """ + let bindings(schemaJSON: File!): File! { + codegenBase + .withMountedFile(schemaPath, schemaJSON) + .withExec([ + "uv", "run", "--isolated", "--frozen", "--package", "codegen", + "python", "-m", "codegen", "generate", "-i", schemaPath, "-o", "/gen.py", + ]) + .file("/gen.py") + } + + """ + Container with this SDK's client library and code generator mounted. + """ + let codegenBase: Container! { + container + .from(codegenImage) + .withoutEntrypoint + .withMountedCache("/root/.cache/uv", cacheVolume("python-sdk-uv")) + .withEnvVariable("UV_LINK_MODE", "copy") + .withDirectory("/sdk", codegenSource) + .withWorkdir("/sdk") + } + + """ + What the code generator needs to run: the library plus the generator itself, + and the lock that pins the generator's own dependencies. + """ + let codegenSource: Directory! { + currentModule.source.directory("sdk").filter(include: [ + "pyproject.toml", + "uv.lock", + "src/**/*.py", + "src/**/*.typed", + "codegen/pyproject.toml", + "codegen/**/*.py", + ]) + } + + """ + What a module actually needs vendored: the importable client library, its + license, and a project file describing just that. + + The code generator runs in this SDK, never in a module, and the library's own + lock pins the generator's development environment rather than the module's. + """ + let library: Directory! { + currentModule.source + .directory("sdk") + .filter(include: [ + "LICENSE", + "README.md", + "src/**/*.py", + "src/**/*.typed", + # The library imports this under suppress(ModuleNotFoundError) and says + # it "doesn't make sense in modules": it provisions an engine for a + # standalone script, which a module already has. + "!src/dagger/provisioning/**", + ]) + .withFile("pyproject.toml", libraryPyproject) + } + + """ + The library's project file with its development sections removed. + + As published, it declares the code generator as a uv workspace member and a + dev dependency. Vendoring that verbatim without the generator makes `uv` + refuse to install the library at all, so the sections that only describe + developing this SDK are dropped. + """ + let libraryPyproject: File! { + codegenBase + .withFile(stripScriptPath, currentModule.source.file("helpers/vendor-pyproject/strip_dev_sections.py")) + .withExec(["python", stripScriptPath, "pyproject.toml", "/library-pyproject.toml"]) + .file("/library-pyproject.toml") + } + + let stripScriptPath: String! = "/strip-dev-sections.py" + + let vendorDirName: String! = "sdk" + let generatedBindingsPath: String! = "src/dagger/client/gen.py" + let schemaPath: String! = "/schema.json" + let codegenImage: String! = "ghcr.io/astral-sh/uv:python3.14-alpine" } diff --git a/runtime/.gitattributes b/runtime/.gitattributes new file mode 100644 index 0000000..6e0a34d --- /dev/null +++ b/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/runtime/.gitignore b/runtime/.gitignore new file mode 100644 index 0000000..f10862a --- /dev/null +++ b/runtime/.gitignore @@ -0,0 +1 @@ +/.env diff --git a/runtime/dagger-module.toml b/runtime/dagger-module.toml new file mode 100644 index 0000000..711b4ee --- /dev/null +++ b/runtime/dagger-module.toml @@ -0,0 +1,9 @@ +name = "python-sdk-runtime" +engineVersion = "v1.0.0-0" +include = ["!**/.venv", "!**/__pycache__", "!**/*.pyc"] + +[runtime] + source = "go" + +[codegen] + automaticGitignore = false diff --git a/runtime/dagger.gen.go b/runtime/dagger.gen.go new file mode 100644 index 0000000..c762970 --- /dev/null +++ b/runtime/dagger.gen.go @@ -0,0 +1,299 @@ +// Code generated by dagger. DO NOT EDIT. + +package main + +import ( + "context" + "encoding/json" + "fmt" + "log/slog" + "os" + "sort" + + telemetry "github.com/dagger/otel-go" + "github.com/vektah/gqlparser/v2/gqlerror" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/sdk/resource" + semconv "go.opentelemetry.io/otel/semconv/v1.40.0" + "go.opentelemetry.io/otel/trace" + + "python-sdk-runtime/internal/dagger" + + "github.com/dagger/querybuilder" +) + +var dag = dagger.Connect() + +func Tracer() trace.Tracer { + return otel.Tracer("dagger.io/sdk.go") +} + +// used for local MarshalJSON implementations +var marshalCtx = context.Background() + +// called by main() +func setMarshalContext(ctx context.Context) { + marshalCtx = ctx + dagger.SetMarshalContext(ctx) +} + +type DaggerObject interface { + querybuilder.GraphQLMarshaller + ID(ctx context.Context) (dagger.ID, error) +} + +type ExecError = dagger.ExecError + +// ptr returns a pointer to the given value. +func ptr[T any](v T) *T { + return &v +} + +// convertSlice converts a slice of one type to a slice of another type using a +// converter function +func convertSlice[I any, O any](in []I, f func(I) O) []O { + out := make([]O, len(in)) + for i, v := range in { + out[i] = f(v) + } + return out +} + +func (r PythonSdkRuntime) MarshalJSON() ([]byte, error) { + var concrete struct { + Container *dagger.Container + Debug bool + ModName string + ProjectName string + PackageName string + MainObjectName string + ModSource *dagger.ModuleSource + ContextDir *dagger.Directory + ContextDirPath string + SubPath string + VendorPath string + IsInit bool + Discovery *Discovery + } + concrete.Container = r.Container + concrete.Debug = r.Debug + concrete.ModName = r.ModName + concrete.ProjectName = r.ProjectName + concrete.PackageName = r.PackageName + concrete.MainObjectName = r.MainObjectName + concrete.ModSource = r.ModSource + concrete.ContextDir = r.ContextDir + concrete.ContextDirPath = r.ContextDirPath + concrete.SubPath = r.SubPath + concrete.VendorPath = r.VendorPath + concrete.IsInit = r.IsInit + concrete.Discovery = r.Discovery + return json.Marshal(&concrete) +} + +func (r *PythonSdkRuntime) UnmarshalJSON(bs []byte) error { + var concrete struct { + Container *dagger.Container + Debug bool + ModName string + ProjectName string + PackageName string + MainObjectName string + ModSource *dagger.ModuleSource + ContextDir *dagger.Directory + ContextDirPath string + SubPath string + VendorPath string + IsInit bool + Discovery *Discovery + } + err := json.Unmarshal(bs, &concrete) + if err != nil { + return err + } + r.Container = concrete.Container + r.Debug = concrete.Debug + r.ModName = concrete.ModName + r.ProjectName = concrete.ProjectName + r.PackageName = concrete.PackageName + r.MainObjectName = concrete.MainObjectName + r.ModSource = concrete.ModSource + r.ContextDir = concrete.ContextDir + r.ContextDirPath = concrete.ContextDirPath + r.SubPath = concrete.SubPath + r.VendorPath = concrete.VendorPath + r.IsInit = concrete.IsInit + r.Discovery = concrete.Discovery + return nil +} + +func main() { + ctx := context.Background() + + // Direct slog to the new stderr. This is only for dev time debugging, and + // runtime errors/warnings. + slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ + Level: slog.LevelWarn, + }))) + + if err := dispatch(ctx); err != nil { + os.Exit(2) + } +} + +func convertError(rerr error) *dagger.Error { + if gqlErr := findSingleGQLError(rerr); gqlErr != nil { + dagErr := dag.Error(gqlErr.Message) + if gqlErr.Extensions != nil { + keys := make([]string, 0, len(gqlErr.Extensions)) + for k := range gqlErr.Extensions { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + val, err := json.Marshal(gqlErr.Extensions[k]) + if err != nil { + fmt.Println("failed to marshal error value:", err) + } + dagErr = dagErr.WithValue(k, dagger.JSON(val)) + } + } + return dagErr + } + return dag.Error(rerr.Error()) +} + +func findSingleGQLError(rerr error) *gqlerror.Error { + switch x := rerr.(type) { + case *gqlerror.Error: + return x + case interface{ Unwrap() []error }: + return nil + case interface{ Unwrap() error }: + return findSingleGQLError(x.Unwrap()) + default: + return nil + } +} +func dispatch(ctx context.Context) (rerr error) { + ctx = telemetry.InitEmbedded(ctx, resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceNameKey.String("dagger-go-sdk"), + // TODO version? + )) + defer telemetry.Close() + + // A lot of the "work" actually happens when we're marshalling the return + // value, which entails getting object IDs, which happens in MarshalJSON, + // which has no ctx argument, so we use this lovely global variable. + setMarshalContext(ctx) + + fnCall := dag.CurrentFunctionCall() + defer func() { + if rerr != nil { + if err := fnCall.ReturnError(ctx, convertError(rerr)); err != nil { + fmt.Println("failed to return error:", err, "\noriginal error:", rerr) + } + } + }() + + parentName, err := fnCall.ParentName(ctx) + if err != nil { + return fmt.Errorf("get parent name: %w", err) + } + fnName, err := fnCall.Name(ctx) + if err != nil { + return fmt.Errorf("get fn name: %w", err) + } + parentJson, err := fnCall.Parent(ctx) + if err != nil { + return fmt.Errorf("get fn parent: %w", err) + } + fnArgs, err := fnCall.InputArgs(ctx) + if err != nil { + return fmt.Errorf("get fn args: %w", err) + } + + inputArgs := map[string][]byte{} + for _, fnArg := range fnArgs { + argName, err := fnArg.Name(ctx) + if err != nil { + return fmt.Errorf("get fn arg name: %w", err) + } + argValue, err := fnArg.Value(ctx) + if err != nil { + return fmt.Errorf("get fn arg value: %w", err) + } + inputArgs[argName] = []byte(argValue) + } + + result, err := invoke(ctx, []byte(parentJson), parentName, fnName, inputArgs) + if err != nil { + return err + } + resultBytes, err := json.Marshal(result) + if err != nil { + return fmt.Errorf("marshal: %w", err) + } + + if err := fnCall.ReturnValue(ctx, dagger.JSON(resultBytes)); err != nil { + return fmt.Errorf("store return value: %w", err) + } + return nil +} +func invoke(ctx context.Context, parentJSON []byte, parentName string, fnName string, inputArgs map[string][]byte) (_ any, err error) { + _ = inputArgs + switch parentName { + case "PythonSdkRuntime": + switch fnName { + case "ModuleRuntime": + var parent PythonSdkRuntime + err = json.Unmarshal(parentJSON, &parent) + if err != nil { + panic(fmt.Errorf("%s: %w", "failed to unmarshal parent object", err)) + } + var modSource *dagger.ModuleSource + if inputArgs["modSource"] != nil { + err = json.Unmarshal([]byte(inputArgs["modSource"]), &modSource) + if err != nil { + panic(fmt.Errorf("%s: %w", "failed to unmarshal input arg modSource", err)) + } + } + var introspectionJson *dagger.File + if inputArgs["introspectionJSON"] != nil { + err = json.Unmarshal([]byte(inputArgs["introspectionJSON"]), &introspectionJson) + if err != nil { + panic(fmt.Errorf("%s: %w", "failed to unmarshal input arg introspectionJSON", err)) + } + } + return (*PythonSdkRuntime).ModuleRuntime(&parent, ctx, modSource, introspectionJson) + case "": + var parent PythonSdkRuntime + err = json.Unmarshal(parentJSON, &parent) + if err != nil { + panic(fmt.Errorf("%s: %w", "failed to unmarshal parent object", err)) + } + return New() + default: + return nil, fmt.Errorf("unknown function %s", fnName) + } + case "": + return dag.Module(). + WithDescription("Runtime module for the Python SDK\n"). + WithObject( + dag.TypeDef().WithObject("PythonSdkRuntime", dagger.TypeDefWithObjectOpts{Description: "State threaded through the steps that build a module's runtime container.\n\nModuleRuntime is the only thing the engine calls; everything else here is\ninternal to it.", SourceMap: dag.SourceMap("main.go", 65, 6)}). + WithFunction( + dag.Function("ModuleRuntime", + dag.TypeDef().WithObject("Container")). + WithDescription("Container for executing the Python module runtime\n\nThe container is built from the module's committed generated files. This\nruntime generates nothing: code generation belongs to `dagger generate`,\nwhich the Python SDK module owns. Dependencies are still installed — the\nlanguage-level assemble step, like the Go SDK still running go build.\n\nintrospectionJSON is declared, and never read, on purpose: its optionality is\nthe signal the engine reads (RuntimeTrustsCommittedFiles) to decide it may\nskip runtime codegen and omit the argument altogether. Dropping the argument\nwould tell the engine the opposite. It is the opt-out, not a code path."). + WithSourceMap(dag.SourceMap("main.go", 143, 1)). + WithArg("modSource", dag.TypeDef().WithObject("ModuleSource"), dagger.FunctionWithArgOpts{SourceMap: dag.SourceMap("main.go", 145, 2)}). + WithArg("introspectionJSON", dag.TypeDef().WithObject("File").WithOptional(true), dagger.FunctionWithArgOpts{SourceMap: dag.SourceMap("main.go", 147, 2)})). + WithConstructor( + dag.Function("New", + dag.TypeDef().WithObject("PythonSdkRuntime")). + WithSourceMap(dag.SourceMap("main.go", 45, 1)))), nil + default: + return nil, fmt.Errorf("unknown object %s", parentName) + } +} diff --git a/runtime/discovery.go b/runtime/discovery.go new file mode 100644 index 0000000..38adc61 --- /dev/null +++ b/runtime/discovery.go @@ -0,0 +1,435 @@ +package main + +import ( + "context" + "fmt" + "path" + "python-sdk-runtime/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 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 + + + // 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), + FileSet: make(map[string]struct{}), + Files: make(map[string]string), + + }, 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 +} + +// GetFile returns a file from the module's source. +func (m *PythonSdkRuntime) 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 *PythonSdkRuntime) useUvLock() bool { + d := m.Discovery + return m.useUv() && (d.HasFile(UvLock) || !d.HasFile(PipCompileLock) && m.IsInit) +} + +// We could use modSource.Directory("") but we'll need to use the +// context directory 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 *PythonSdkRuntime) source() *dagger.Directory { + return m.ContextDir.Directory(m.SubPath) +} + +// getImage returns the container image address for the given name. +func (m *PythonSdkRuntime) 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 *PythonSdkRuntime) error { + type loadFunc func(context.Context, *PythonSdkRuntime) 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 *PythonSdkRuntime) 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 module runtime 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 *PythonSdkRuntime) 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 exclude == GenDir { + // The committed vendored sdk is what this runtime builds from. + continue + } + if d.HasFile(exclude) { + m.ContextDir = m.ContextDir.WithoutDirectory( + path.Join(m.SubPath, exclude), + ) + } + } + + eg, gctx := errgroup.WithContext(ctx) + + { + for _, name := range FileContents { + 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 + }) + + return eg.Wait() +} + +// loadConfig loads configurations from user files listed in FileContents. +func (d *Discovery) loadConfig(ctx context.Context, m *PythonSdkRuntime) error { + // `pyproject.toml` can only be empty on `dagger init`, in which case it is + // created by this SDK's initModule. + 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.13" +// ``` +// 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/runtime/go.mod b/runtime/go.mod new file mode 100644 index 0000000..2e694d4 --- /dev/null +++ b/runtime/go.mod @@ -0,0 +1,65 @@ +module python-sdk-runtime + +go 1.26.1 + +require ( + github.com/dagger/otel-go v1.41.0 + 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.11.1 + golang.org/x/sync v0.20.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.90 // indirect + github.com/Khan/genqlient v0.8.1 + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/dagger/querybuilder v0.0.0-20260402040506-574a5e81cb59 + github.com/go-logr/logr v1.4.3 // 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.28.0 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect + github.com/sosodev/duration v1.4.0 // indirect + github.com/vektah/gqlparser/v2 v2.5.33 + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel v1.44.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.17.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.17.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.41.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.41.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0 // indirect + go.opentelemetry.io/otel/log v0.17.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/sdk v1.41.0 + go.opentelemetry.io/otel/sdk/log v0.17.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.41.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 + go.opentelemetry.io/proto/otlp v1.9.0 // indirect + golang.org/x/net v0.52.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/text v0.35.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect + google.golang.org/grpc v1.79.3 // indirect + google.golang.org/protobuf v1.36.11 // indirect +) + +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 + +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 + +replace go.opentelemetry.io/otel/log => go.opentelemetry.io/otel/log v0.16.0 + +replace go.opentelemetry.io/otel/sdk/log => go.opentelemetry.io/otel/sdk/log v0.16.0 diff --git a/runtime/go.sum b/runtime/go.sum new file mode 100644 index 0000000..c90aa2e --- /dev/null +++ b/runtime/go.sum @@ -0,0 +1,129 @@ +github.com/99designs/gqlgen v0.17.90 h1:wSv6blm/PoplU6QoNw83EcQpNtC0HX3/+44vITJOzpk= +github.com/99designs/gqlgen v0.17.90/go.mod h1:GqYrEwYsqCG8VaOsq2kJUCUKwAE1T+u2i+Nj7NtXiVI= +github.com/Khan/genqlient v0.8.1 h1:wtOCc8N9rNynRLXN3k3CnfzheCUNKBcvXmVv5zt6WCs= +github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU= +github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= +github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= +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/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/dagger/otel-go v1.41.0 h1:GQAJtTM1Ja9Dt/JSSqqjCFVlCye09Ymx4dWUDRqcgKw= +github.com/dagger/otel-go v1.41.0/go.mod h1:RP74B3xmOq2MWL1lBsAWD9uvTryDhZ+m1dDzJj9QJEI= +github.com/dagger/querybuilder v0.0.0-20260402040506-574a5e81cb59 h1:g6vfdGRyz6fAjfHz5FyYPZgHy8qcQ31fHrBl1iCOzxw= +github.com/dagger/querybuilder v0.0.0-20260402040506-574a5e81cb59/go.mod h1:jsdUJeYzcbyK1j/EqMGPrQgNYxl/Zfg06vvM9C/xXxs= +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.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/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.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.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= +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.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +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.4.0 h1:35ed0KiVFriGHHzZZJaZLgmTEEICIyt8Sx0RQfj9IjE= +github.com/sosodev/duration v1.4.0/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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/vektah/gqlparser/v2 v2.5.33 h1:lRp8aIeNUNbimf/axZd7ETg24q06hBtPaas+TcvI/7E= +github.com/vektah/gqlparser/v2 v2.5.33/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 h1:ZVg+kCXxd9LtAaQNKBxAvJ5NpMf7LpvEr4MIZqb0TMQ= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0/go.mod h1:hh0tMeZ75CCXrHd9OXRYxTlCAdxcXioWHFIpYw2rZu8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 h1:djrxvDxAe44mJUrKataUbOhCKhR3F8QCyWucO16hTQs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0/go.mod h1:dt3nxpQEiSoKvfTVxp3TUg5fHPLhKtbcnN3Z1I1ePD0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.41.0 h1:VO3BL6OZXRQ1yQc8W6EVfJzINeJ35BkiHx4MYfoQf44= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.41.0/go.mod h1:qRDnJ2nv3CQXMK2HUd9K9VtvedsPAce3S+/4LZHjX/s= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.41.0 h1:MMrOAN8H1FrvDyq9UJ4lu5/+ss49Qgfgb7Zpm0m8ABo= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.41.0/go.mod h1:Na+2NNASJtF+uT4NxDe0G+NQb+bUgdPDfwxY/6JmS/c= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0 h1:ao6Oe+wSebTlQ1OEht7jlYTzQKE+pnx/iNywFvTbuuI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.41.0/go.mod h1:u3T6vz0gh/NVzgDgiwkgLxpsSF6PaPmo2il0apGJbls= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0 h1:mq/Qcf28TWz719lE3/hMB4KkyDuLJIvgJnFGcd0kEUI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0/go.mod h1:yk5LXEYhsL2htyDNJbEq7fWzNEigeEdV5xBF/Y+kAv0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0 h1:inYW9ZhgqiDqh6BioM7DVHHzEGVq76Db5897WLGZ5Go= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0/go.mod h1:Izur+Wt8gClgMJqO/cZ8wdeeMryJ/xxiOVgFSSfpDTY= +go.opentelemetry.io/otel/log v0.16.0 h1:DeuBPqCi6pQwtCK0pO4fvMB5eBq6sNxEnuTs88pjsN4= +go.opentelemetry.io/otel/log v0.16.0/go.mod h1:rWsmqNVTLIA8UnwYVOItjyEZDbKIkMxdQunsIhpUMes= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.41.0 h1:YPIEXKmiAwkGl3Gu1huk1aYWwtpRLeskpV+wPisxBp8= +go.opentelemetry.io/otel/sdk v1.41.0/go.mod h1:ahFdU0G5y8IxglBf0QBJXgSe7agzjE4GiTJ6HT9ud90= +go.opentelemetry.io/otel/sdk/log v0.16.0 h1:e/b4bdlQwC5fnGtG3dlXUrNOnP7c8YLVSpSfEBIkTnI= +go.opentelemetry.io/otel/sdk/log v0.16.0/go.mod h1:JKfP3T6ycy7QEuv3Hj8oKDy7KItrEkus8XJE6EoSzw4= +go.opentelemetry.io/otel/sdk/log/logtest v0.16.0 h1:/XVkpZ41rVRTP4DfMgYv1nEtNmf65XPPyAdqV90TMy4= +go.opentelemetry.io/otel/sdk/log/logtest v0.16.0/go.mod h1:iOOPgQr5MY9oac/F5W86mXdeyWZGleIx3uXO98X2R6Y= +go.opentelemetry.io/otel/sdk/metric v1.41.0 h1:siZQIYBAUd1rlIWQT2uCxWJxcCO7q3TriaMlf08rXw8= +go.opentelemetry.io/otel/sdk/metric v1.41.0/go.mod h1:HNBuSvT7ROaGtGI50ArdRLUnvRTRGniSUZbxiWxSO8Y= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= +go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= +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.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4= +google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +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/runtime/image.go b/runtime/image.go new file mode 100644 index 0000000..6180bef --- /dev/null +++ b/runtime/image.go @@ -0,0 +1,136 @@ +package main + +import ( + _ "embed" + "encoding/json" + "fmt" + "regexp" + "strings" + + "github.com/distribution/reference" +) + +//go:embed images/base/Dockerfile +var baseDockerfile string + +//go:embed images/uv/Dockerfile +var uvDockerfile 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) { + images := make(map[string]Image) + for _, dockerfile := range []string{baseDockerfile, uvDockerfile} { + lines := strings.Split(dockerfile, "\n") + + 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/runtime/images/base/Dockerfile b/runtime/images/base/Dockerfile new file mode 100644 index 0000000..5778df3 --- /dev/null +++ b/runtime/images/base/Dockerfile @@ -0,0 +1 @@ +FROM python:3.14-slim@sha256:486b8092bfb12997e10d4920897213a06563449c951c5506c2a2cfaf591c599f AS base diff --git a/runtime/images/uv/Dockerfile b/runtime/images/uv/Dockerfile new file mode 100644 index 0000000..ab7012d --- /dev/null +++ b/runtime/images/uv/Dockerfile @@ -0,0 +1 @@ +FROM ghcr.io/astral-sh/uv:0.10.3@sha256:7a88d4c4e6f44200575000638453a5a381db0ae31ad5c3a51b14f8687c9d93a3 AS uv diff --git a/runtime/internal/dagger/dagger.gen.go b/runtime/internal/dagger/dagger.gen.go new file mode 100644 index 0000000..9454c93 --- /dev/null +++ b/runtime/internal/dagger/dagger.gen.go @@ -0,0 +1,19323 @@ +// Code generated by dagger. DO NOT EDIT. + +package dagger + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net" + "net/http" + "os" + "reflect" + "strconv" + + "github.com/Khan/genqlient/graphql" + "github.com/vektah/gqlparser/v2/gqlerror" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/trace" + + "github.com/dagger/querybuilder" + + telemetry "github.com/dagger/otel-go" +) + +func Tracer() trace.Tracer { + return otel.Tracer("dagger.io/sdk.go") +} + +// reassigned at runtime after the span is initialized +var marshalCtx = context.Background() + +// SetMarshalContext is a hack that lets us set the ctx to use for +// MarshalJSON implementations that get an object's ID. +func SetMarshalContext(ctx context.Context) { + marshalCtx = ctx +} + +// assertNotNil panic if the given value is nil. +// This function is used to validate that input with pointer type are not nil. +// See https://github.com/dagger/dagger/issues/5696 for more context. +func assertNotNil(argName string, value any) { + // We use reflect because just comparing value to nil is not working since + // the value is wrapped into a type when passed as parameter. + // E.g., nil become (*dagger.File)(nil). + if reflect.ValueOf(value).IsNil() { + panic(fmt.Sprintf("unexpected nil pointer for argument %q", argName)) + } +} + +type DaggerObject interface { + querybuilder.GraphQLMarshaller + ID(ctx context.Context) (ID, error) +} + +type gqlExtendedError struct { + inner *gqlerror.Error +} + +// Same as telemetry.ExtendedError, but without the dependency, to simplify +// client generation. +type extendedError interface { + error + Extensions() map[string]any +} + +func (e gqlExtendedError) Unwrap() error { + return e.inner +} + +var _ extendedError = gqlExtendedError{} + +func (e gqlExtendedError) Error() string { + return e.inner.Message +} + +func (e gqlExtendedError) Extensions() map[string]any { + return e.inner.Extensions +} + +// getCustomError parses a GraphQL error into a more specific error type. +func getCustomError(err error) error { + var gqlErr *gqlerror.Error + if !errors.As(err, &gqlErr) { + return nil + } + + ext := gqlErr.Extensions + + lessNoisyErr := gqlExtendedError{gqlErr} + + typ, ok := ext["_type"].(string) + if !ok { + return lessNoisyErr + } + + if typ == "EXEC_ERROR" { + e := &ExecError{ + original: lessNoisyErr, + } + if code, ok := ext["exitCode"].(float64); ok { + e.ExitCode = int(code) + } + if args, ok := ext["cmd"].([]interface{}); ok { + cmd := make([]string, len(args)) + for i, v := range args { + cmd[i] = v.(string) + } + e.Cmd = cmd + } + if stdout, ok := ext["stdout"].(string); ok { + e.Stdout = stdout + } + if stderr, ok := ext["stderr"].(string); ok { + e.Stderr = stderr + } + return e + } + + return lessNoisyErr +} + +// ExecError is an API error from an exec operation. +type ExecError struct { + original extendedError + Cmd []string + ExitCode int + Stdout string + Stderr string +} + +var _ extendedError = (*ExecError)(nil) + +func (e *ExecError) Error() string { + return e.Message() +} + +func (e *ExecError) Extensions() map[string]any { + return e.original.Extensions() +} + +func (e *ExecError) Message() string { + return e.original.Error() +} + +func (e *ExecError) Unwrap() error { + return e.original +} + +// A unique identifier for an object. +type ID string + +// An arbitrary JSON-encoded value. +type JSON string + +// The platform config OS and architecture in a Container. +// +// The format is [os]/[platform]/[version] (e.g., "darwin/arm64/v7", "windows/amd64", "linux/arm64"). +type Platform string + +// The absence of a value. +// +// A Null Void is used as a placeholder for resolvers that do not return anything. +type Void string + +// Key value object that represents a build argument. +type BuildArg struct { + // The build argument name. + Name string `json:"name"` + + // The build argument value. + Value string `json:"value"` +} + +// A content block within an LLM message. +type LLMContentBlockInput struct { + // The arguments to pass to the tool (for TOOL_CALL kind). + Arguments JSON `json:"arguments"` + + // The unique ID of a tool call (for TOOL_CALL or TOOL_RESULT kinds). + CallID string `json:"callId,omitempty"` + + // Whether the tool call resulted in an error (for TOOL_RESULT kind). + Errored bool `json:"errored,omitempty"` + + // The kind of content block. + Kind LLMContentBlockKind `json:"kind"` + + // Provider-specific opaque data (e.g. Anthropic thinking signature). + Signature string `json:"signature,omitempty"` + + // Text content (for TEXT, THINKING, or TOOL_RESULT kinds). + Text string `json:"text,omitempty"` + + // The name of the tool to call (for TOOL_CALL kind). + ToolName string `json:"toolName,omitempty"` +} + +// Key value object that represents a pipeline label. +type PipelineLabel struct { + // Label name. + Name string `json:"name"` + + // Label value. + Value string `json:"value"` +} + +// Port forwarding rules for tunneling network traffic. +type PortForward struct { + // Destination port for traffic. + Backend int `json:"backend"` + + // Port to expose to clients. If unspecified, a default will be chosen. + Frontend int `json:"frontend"` + + // Transport layer protocol to use for traffic. + Protocol NetworkProtocol `json:"protocol,omitempty"` +} + +// A standardized address to load containers, directories, secrets, and other object types. Address format depends on the type, and is validated at type selection. +type Address struct { + query *querybuilder.Selection + + id *ID + value *string +} + +func (r *Address) WithGraphQLQuery(q *querybuilder.Selection) *Address { + return &Address{ + query: q, + } +} + +// Load a container from the address. +func (r *Address) Container() *Container { + q := r.query.Select("container") + + return &Container{ + query: q, + } +} + +// AddressDirectoryOpts contains options for Address.Directory +type AddressDirectoryOpts struct { + Exclude []string + + Include []string + + Gitignore bool + + NoCache bool +} + +// Load a directory from the address. +func (r *Address) Directory(opts ...AddressDirectoryOpts) *Directory { + q := r.query.Select("directory") + for i := len(opts) - 1; i >= 0; i-- { + // `exclude` optional argument + if !querybuilder.IsZeroValue(opts[i].Exclude) { + q = q.Arg("exclude", opts[i].Exclude) + } + // `include` optional argument + if !querybuilder.IsZeroValue(opts[i].Include) { + q = q.Arg("include", opts[i].Include) + } + // `gitignore` optional argument + if !querybuilder.IsZeroValue(opts[i].Gitignore) { + q = q.Arg("gitignore", opts[i].Gitignore) + } + // `noCache` optional argument + if !querybuilder.IsZeroValue(opts[i].NoCache) { + q = q.Arg("noCache", opts[i].NoCache) + } + } + + return &Directory{ + query: q, + } +} + +// AddressFileOpts contains options for Address.File +type AddressFileOpts struct { + Exclude []string + + Include []string + + Gitignore bool + + NoCache bool +} + +// Load a file from the address. +func (r *Address) File(opts ...AddressFileOpts) *File { + q := r.query.Select("file") + for i := len(opts) - 1; i >= 0; i-- { + // `exclude` optional argument + if !querybuilder.IsZeroValue(opts[i].Exclude) { + q = q.Arg("exclude", opts[i].Exclude) + } + // `include` optional argument + if !querybuilder.IsZeroValue(opts[i].Include) { + q = q.Arg("include", opts[i].Include) + } + // `gitignore` optional argument + if !querybuilder.IsZeroValue(opts[i].Gitignore) { + q = q.Arg("gitignore", opts[i].Gitignore) + } + // `noCache` optional argument + if !querybuilder.IsZeroValue(opts[i].NoCache) { + q = q.Arg("noCache", opts[i].NoCache) + } + } + + return &File{ + query: q, + } +} + +// Load a git ref (branch, tag or commit) from the address. +func (r *Address) GitRef() *GitRef { + q := r.query.Select("gitRef") + + return &GitRef{ + query: q, + } +} + +// Load a git repository from the address. +func (r *Address) GitRepository() *GitRepository { + q := r.query.Select("gitRepository") + + return &GitRepository{ + query: q, + } +} + +// A unique identifier for this Address. +func (r *Address) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Address) XXX_GraphQLType() string { + return "Address" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Address) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Address) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Address) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Address) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = Address{query: selectNode(dag.query, id, "Address")} + return nil +} + +// Load a secret from the address. +func (r *Address) Secret() *Secret { + q := r.query.Select("secret") + + return &Secret{ + query: q, + } +} + +// Load a service from the address. +func (r *Address) Service() *Service { + q := r.query.Select("service") + + return &Service{ + query: q, + } +} + +// Load a local socket from the address. +func (r *Address) Socket() *Socket { + q := r.query.Select("socket") + + return &Socket{ + query: q, + } +} + +// The address value +func (r *Address) Value(ctx context.Context) (string, error) { + if r.value != nil { + return *r.value, nil + } + q := r.query.Select("value") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this Address as a Node. +// This is a local type conversion — no GraphQL call. +func (r *Address) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +type Agent struct { + query *querybuilder.Selection + + description *string + id *ID + name *string +} + +func (r *Agent) WithGraphQLQuery(q *querybuilder.Selection) *Agent { + return &Agent{ + query: q, + } +} + +// The description of the agent +func (r *Agent) Description(ctx context.Context) (string, error) { + if r.description != nil { + return *r.description, nil + } + q := r.query.Select("description") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this Agent. +func (r *Agent) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Agent) XXX_GraphQLType() string { + return "Agent" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Agent) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Agent) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Agent) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Agent) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = Agent{query: selectNode(dag.query, id, "Agent")} + return nil +} + +// Return the fully qualified name of the agent +func (r *Agent) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The original module in which the agent has been defined +func (r *Agent) OriginalModule() *Module { + q := r.query.Select("originalModule") + + return &Module{ + query: q, + } +} + +// The path of the agent within its module +func (r *Agent) Path(ctx context.Context) ([]string, error) { + q := r.query.Select("path") + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this Agent as a Node. +// This is a local type conversion — no GraphQL call. +func (r *Agent) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +type AgentGroup struct { + query *querybuilder.Selection + + id *ID +} + +func (r *AgentGroup) WithGraphQLQuery(q *querybuilder.Selection) *AgentGroup { + return &AgentGroup{ + query: q, + } +} + +// AgentGroupComposeOpts contains options for AgentGroup.Compose +type AgentGroupComposeOpts struct { + // The base LLM to compose onto. Defaults to a fresh workspace-bound LLM. + Base *LLM +} + +// Compose all selected agent middlewares onto a base LLM, in alphabetical module:fn order, and return the composed LLM. +func (r *AgentGroup) Compose(opts ...AgentGroupComposeOpts) *LLM { + q := r.query.Select("compose") + for i := len(opts) - 1; i >= 0; i-- { + // `base` optional argument + if !querybuilder.IsZeroValue(opts[i].Base) { + q = q.Arg("base", opts[i].Base) + } + } + + return &LLM{ + query: q, + } +} + +// A unique identifier for this AgentGroup. +func (r *AgentGroup) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *AgentGroup) XXX_GraphQLType() string { + return "AgentGroup" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *AgentGroup) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *AgentGroup) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *AgentGroup) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *AgentGroup) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = AgentGroup{query: selectNode(dag.query, id, "AgentGroup")} + return nil +} + +// Return a list of individual agents and their details +func (r *AgentGroup) List(ctx context.Context) ([]Agent, error) { + q := r.query.Select("list") + + q = q.Select("id") + + type list struct { + Id ID + } + + convert := func(fields []list) []Agent { + out := []Agent{} + + for i := range fields { + val := Agent{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "Agent") + out = append(out, val) + } + + return out + } + var response []list + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// AsNode returns this AgentGroup as a Node. +// This is a local type conversion — no GraphQL call. +func (r *AgentGroup) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A directory whose contents persist across runs. +type CacheVolume struct { + query *querybuilder.Selection + + id *ID +} + +func (r *CacheVolume) WithGraphQLQuery(q *querybuilder.Selection) *CacheVolume { + return &CacheVolume{ + query: q, + } +} + +// A unique identifier for this CacheVolume. +func (r *CacheVolume) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *CacheVolume) XXX_GraphQLType() string { + return "CacheVolume" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *CacheVolume) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *CacheVolume) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *CacheVolume) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *CacheVolume) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = CacheVolume{query: selectNode(dag.query, id, "CacheVolume")} + return nil +} + +// AsNode returns this CacheVolume as a Node. +// This is a local type conversion — no GraphQL call. +func (r *CacheVolume) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A comparison between two directories representing changes that can be applied. +type Changeset struct { + query *querybuilder.Selection + + export *string + id *ID + isEmpty *bool + sync *ID +} +type WithChangesetFunc func(r *Changeset) *Changeset + +// With calls the provided function with current Changeset. +// +// This is useful for reusability and readability by not breaking the calling chain. +func (r *Changeset) With(f WithChangesetFunc) *Changeset { + return f(r) +} + +func (r *Changeset) WithGraphQLQuery(q *querybuilder.Selection) *Changeset { + return &Changeset{ + query: q, + } +} + +// Files and directories that were added in the newer directory. +func (r *Changeset) AddedPaths(ctx context.Context) ([]string, error) { + q := r.query.Select("addedPaths") + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The newer/upper snapshot. +func (r *Changeset) After() *Directory { + q := r.query.Select("after") + + return &Directory{ + query: q, + } +} + +// Return a Git-compatible patch of the changes +func (r *Changeset) AsPatch() *File { + q := r.query.Select("asPatch") + + return &File{ + query: q, + } +} + +// The older/lower snapshot to compare against. +func (r *Changeset) Before() *Directory { + q := r.query.Select("before") + + return &Directory{ + query: q, + } +} + +// Structured per-path diff statistics (kind and line counts) for this changeset. +func (r *Changeset) DiffStats(ctx context.Context) ([]DiffStat, error) { + q := r.query.Select("diffStats") + + q = q.Select("id") + + type diffStats struct { + Id ID + } + + convert := func(fields []diffStats) []DiffStat { + out := []DiffStat{} + + for i := range fields { + val := DiffStat{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "DiffStat") + out = append(out, val) + } + + return out + } + var response []diffStats + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// Applies the diff represented by this changeset to a path on the host. +func (r *Changeset) Export(ctx context.Context, path string) (string, error) { + if r.export != nil { + return *r.export, nil + } + q := r.query.Select("export") + q = q.Arg("path", path) + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this Changeset. +func (r *Changeset) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Changeset) XXX_GraphQLType() string { + return "Changeset" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Changeset) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Changeset) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Changeset) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Changeset) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = Changeset{query: selectNode(dag.query, id, "Changeset")} + return nil +} + +// Returns true if the changeset is empty (i.e. there are no changes). +func (r *Changeset) IsEmpty(ctx context.Context) (bool, error) { + if r.isEmpty != nil { + return *r.isEmpty, nil + } + q := r.query.Select("isEmpty") + + var response bool + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Return a snapshot containing only the created and modified files +func (r *Changeset) Layer() *Directory { + q := r.query.Select("layer") + + return &Directory{ + query: q, + } +} + +// Files and directories that existed before and were updated in the newer directory. +func (r *Changeset) ModifiedPaths(ctx context.Context) ([]string, error) { + q := r.query.Select("modifiedPaths") + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Files and directories that were removed. Directories are indicated by a trailing slash, and their child paths are not included. +func (r *Changeset) RemovedPaths(ctx context.Context) ([]string, error) { + q := r.query.Select("removedPaths") + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Force evaluation in the engine. +func (r *Changeset) Sync(ctx context.Context) (*Changeset, error) { + q := r.query.Select("sync") + + var id ID + if err := q.Bind(&id).Execute(ctx); err != nil { + return nil, err + } + return &Changeset{ + query: selectNode(q.Root(), id, "Changeset"), + }, nil +} + +// ChangesetWithChangesetOpts contains options for Changeset.WithChangeset +type ChangesetWithChangesetOpts struct { + // What to do on a merge conflict + // + // Default: FAIL + OnConflict ChangesetMergeConflict +} + +// Add changes to an existing changeset +// +// By default the operation will fail in case of conflicts, for instance a file modified in both changesets. The behavior can be adjusted using onConflict argument +func (r *Changeset) WithChangeset(changes *Changeset, opts ...ChangesetWithChangesetOpts) *Changeset { + assertNotNil("changes", changes) + q := r.query.Select("withChangeset") + for i := len(opts) - 1; i >= 0; i-- { + // `onConflict` optional argument + if !querybuilder.IsZeroValue(opts[i].OnConflict) { + q = q.Arg("onConflict", opts[i].OnConflict) + } + } + q = q.Arg("changes", changes) + + return &Changeset{ + query: q, + } +} + +// ChangesetWithChangesetsOpts contains options for Changeset.WithChangesets +type ChangesetWithChangesetsOpts struct { + // What to do on a merge conflict + // + // Default: FAIL + OnConflict ChangesetsMergeConflict +} + +// Add changes from multiple changesets using git octopus merge strategy +// +// This is more efficient than chaining multiple withChangeset calls when merging many changesets. +// +// Only FAIL and FAIL_EARLY conflict strategies are supported (octopus merge cannot use -X ours/theirs). +func (r *Changeset) WithChangesets(changes []*Changeset, opts ...ChangesetWithChangesetsOpts) *Changeset { + q := r.query.Select("withChangesets") + for i := len(opts) - 1; i >= 0; i-- { + // `onConflict` optional argument + if !querybuilder.IsZeroValue(opts[i].OnConflict) { + q = q.Arg("onConflict", opts[i].OnConflict) + } + } + q = q.Arg("changes", changes) + + return &Changeset{ + query: q, + } +} + +// AsExportable returns this Changeset as a Exportable. +// This is a local type conversion — no GraphQL call. +func (r *Changeset) AsExportable() Exportable { + return &ExportableClient{ + query: r.query, + } +} + +// AsNode returns this Changeset as a Node. +// This is a local type conversion — no GraphQL call. +func (r *Changeset) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// AsSyncer returns this Changeset as a Syncer. +// This is a local type conversion — no GraphQL call. +func (r *Changeset) AsSyncer() Syncer { + return &SyncerClient{ + query: r.query, + } +} + +type Check struct { + query *querybuilder.Selection + + checkType *string + completed *bool + description *string + id *ID + name *string + passed *bool + resultEmoji *string +} +type WithCheckFunc func(r *Check) *Check + +// With calls the provided function with current Check. +// +// This is useful for reusability and readability by not breaking the calling chain. +func (r *Check) With(f WithCheckFunc) *Check { + return f(r) +} + +func (r *Check) WithGraphQLQuery(q *querybuilder.Selection) *Check { + return &Check{ + query: q, + } +} + +// The type of check: 'check' for annotated checks, 'generate' for generate-as-checks +func (r *Check) CheckType(ctx context.Context) (string, error) { + if r.checkType != nil { + return *r.checkType, nil + } + q := r.query.Select("checkType") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Whether the check completed +func (r *Check) Completed(ctx context.Context) (bool, error) { + if r.completed != nil { + return *r.completed, nil + } + q := r.query.Select("completed") + + var response bool + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The description of the check +func (r *Check) Description(ctx context.Context) (string, error) { + if r.description != nil { + return *r.description, nil + } + q := r.query.Select("description") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// If the check failed, this is the error +func (r *Check) Error(ctx context.Context) (*Error, error) { + q := r.query.Select("error") + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &Error{ + query: selectNode(q.Root(), *objectID, "Error"), + }, nil +} + +// A unique identifier for this Check. +func (r *Check) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Check) XXX_GraphQLType() string { + return "Check" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Check) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Check) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Check) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Check) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = Check{query: selectNode(dag.query, id, "Check")} + return nil +} + +// Return the fully qualified name of the check +func (r *Check) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The original module in which the check has been defined +func (r *Check) OriginalModule() *Module { + q := r.query.Select("originalModule") + + return &Module{ + query: q, + } +} + +// Whether the check passed +func (r *Check) Passed(ctx context.Context) (bool, error) { + if r.passed != nil { + return *r.passed, nil + } + q := r.query.Select("passed") + + var response bool + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The path of the check within its module +func (r *Check) Path(ctx context.Context) ([]string, error) { + q := r.query.Select("path") + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// An emoji representing the result of the check +func (r *Check) ResultEmoji(ctx context.Context) (string, error) { + if r.resultEmoji != nil { + return *r.resultEmoji, nil + } + q := r.query.Select("resultEmoji") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Execute the check +func (r *Check) Run() *Check { + q := r.query.Select("run") + + return &Check{ + query: q, + } +} + +// AsNode returns this Check as a Node. +// This is a local type conversion — no GraphQL call. +func (r *Check) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +type CheckGroup struct { + query *querybuilder.Selection + + id *ID +} +type WithCheckGroupFunc func(r *CheckGroup) *CheckGroup + +// With calls the provided function with current CheckGroup. +// +// This is useful for reusability and readability by not breaking the calling chain. +func (r *CheckGroup) With(f WithCheckGroupFunc) *CheckGroup { + return f(r) +} + +func (r *CheckGroup) WithGraphQLQuery(q *querybuilder.Selection) *CheckGroup { + return &CheckGroup{ + query: q, + } +} + +// A unique identifier for this CheckGroup. +func (r *CheckGroup) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *CheckGroup) XXX_GraphQLType() string { + return "CheckGroup" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *CheckGroup) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *CheckGroup) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *CheckGroup) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *CheckGroup) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = CheckGroup{query: selectNode(dag.query, id, "CheckGroup")} + return nil +} + +// Return a list of individual checks and their details +func (r *CheckGroup) List(ctx context.Context) ([]Check, error) { + q := r.query.Select("list") + + q = q.Select("id") + + type list struct { + Id ID + } + + convert := func(fields []list) []Check { + out := []Check{} + + for i := range fields { + val := Check{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "Check") + out = append(out, val) + } + + return out + } + var response []list + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// Generate a markdown report +func (r *CheckGroup) Report() *File { + q := r.query.Select("report") + + return &File{ + query: q, + } +} + +// CheckGroupRunOpts contains options for CheckGroup.Run +type CheckGroupRunOpts struct { + // If true, stop running checks as soon as any check fails. + FailFast bool +} + +// Execute all selected checks +func (r *CheckGroup) Run(opts ...CheckGroupRunOpts) *CheckGroup { + q := r.query.Select("run") + for i := len(opts) - 1; i >= 0; i-- { + // `failFast` optional argument + if !querybuilder.IsZeroValue(opts[i].FailFast) { + q = q.Arg("failFast", opts[i].FailFast) + } + } + + return &CheckGroup{ + query: q, + } +} + +// AsNode returns this CheckGroup as a Node. +// This is a local type conversion — no GraphQL call. +func (r *CheckGroup) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// An internal persistent filesync mirror. +type ClientFilesyncMirror struct { + query *querybuilder.Selection + + id *ID +} + +func (r *ClientFilesyncMirror) WithGraphQLQuery(q *querybuilder.Selection) *ClientFilesyncMirror { + return &ClientFilesyncMirror{ + query: q, + } +} + +// A unique identifier for this ClientFilesyncMirror. +func (r *ClientFilesyncMirror) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *ClientFilesyncMirror) XXX_GraphQLType() string { + return "ClientFilesyncMirror" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *ClientFilesyncMirror) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *ClientFilesyncMirror) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *ClientFilesyncMirror) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *ClientFilesyncMirror) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = ClientFilesyncMirror{query: selectNode(dag.query, id, "ClientFilesyncMirror")} + return nil +} + +// AsNode returns this ClientFilesyncMirror as a Node. +// This is a local type conversion — no GraphQL call. +func (r *ClientFilesyncMirror) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// Dagger Cloud configuration and state +type Cloud struct { + query *querybuilder.Selection + + id *ID + traceURL *string +} + +func (r *Cloud) WithGraphQLQuery(q *querybuilder.Selection) *Cloud { + return &Cloud{ + query: q, + } +} + +// A unique identifier for this Cloud. +func (r *Cloud) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Cloud) XXX_GraphQLType() string { + return "Cloud" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Cloud) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Cloud) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Cloud) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Cloud) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = Cloud{query: selectNode(dag.query, id, "Cloud")} + return nil +} + +// The trace URL for the current session +func (r *Cloud) TraceURL(ctx context.Context) (string, error) { + if r.traceURL != nil { + return *r.traceURL, nil + } + q := r.query.Select("traceURL") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this Cloud as a Node. +// This is a local type conversion — no GraphQL call. +func (r *Cloud) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// An OCI-compatible container, also known as a Docker container. +type Container struct { + query *querybuilder.Selection + + combinedOutput *string + envVariable *string + exists *bool + exitCode *int + export *string + exportImage *Void + id *ID + imageRef *string + label *string + platform *Platform + publish *string + stderr *string + stdout *string + sync *ID + up *Void + user *string + workdir *string +} +type WithContainerFunc func(r *Container) *Container + +// With calls the provided function with current Container. +// +// This is useful for reusability and readability by not breaking the calling chain. +func (r *Container) With(f WithContainerFunc) *Container { + return f(r) +} + +func (r *Container) WithGraphQLQuery(q *querybuilder.Selection) *Container { + return &Container{ + query: q, + } +} + +// ContainerAsServiceOpts contains options for Container.AsService +type ContainerAsServiceOpts struct { + // 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. + Args []string + // If the container has an entrypoint, prepend it to the args. + UseEntrypoint bool + // Provides Dagger access to the executed command. + ExperimentalPrivilegedNesting bool + // 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. + InsecureRootCapabilities bool + // Replace "${VAR}" or "$VAR" in the args according to the current environment variables defined in the container (e.g. "/$VAR/foo"). + Expand bool + // 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. + NoInit bool +} + +// Turn the container into a Service. +// +// Be sure to set any exposed ports before this conversion. +func (r *Container) AsService(opts ...ContainerAsServiceOpts) *Service { + q := r.query.Select("asService") + for i := len(opts) - 1; i >= 0; i-- { + // `args` optional argument + if !querybuilder.IsZeroValue(opts[i].Args) { + q = q.Arg("args", opts[i].Args) + } + // `useEntrypoint` optional argument + if !querybuilder.IsZeroValue(opts[i].UseEntrypoint) { + q = q.Arg("useEntrypoint", opts[i].UseEntrypoint) + } + // `experimentalPrivilegedNesting` optional argument + if !querybuilder.IsZeroValue(opts[i].ExperimentalPrivilegedNesting) { + q = q.Arg("experimentalPrivilegedNesting", opts[i].ExperimentalPrivilegedNesting) + } + // `insecureRootCapabilities` optional argument + if !querybuilder.IsZeroValue(opts[i].InsecureRootCapabilities) { + q = q.Arg("insecureRootCapabilities", opts[i].InsecureRootCapabilities) + } + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + // `noInit` optional argument + if !querybuilder.IsZeroValue(opts[i].NoInit) { + q = q.Arg("noInit", opts[i].NoInit) + } + } + + return &Service{ + query: q, + } +} + +// ContainerAsTarballOpts contains options for Container.AsTarball +type ContainerAsTarballOpts struct { + // Identifiers for other platform specific containers. + // + // Used for multi-platform images. + PlatformVariants []*Container + // 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. + ForcedCompression ImageLayerCompression + // 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. + // + // Default: OCIMediaTypes + MediaTypes ImageMediaTypes +} + +// Package the container state as an OCI image, and return it as a tar archive +func (r *Container) AsTarball(opts ...ContainerAsTarballOpts) *File { + q := r.query.Select("asTarball") + for i := len(opts) - 1; i >= 0; i-- { + // `platformVariants` optional argument + if !querybuilder.IsZeroValue(opts[i].PlatformVariants) { + q = q.Arg("platformVariants", opts[i].PlatformVariants) + } + // `forcedCompression` optional argument + if !querybuilder.IsZeroValue(opts[i].ForcedCompression) { + q = q.Arg("forcedCompression", opts[i].ForcedCompression) + } + // `mediaTypes` optional argument + if !querybuilder.IsZeroValue(opts[i].MediaTypes) { + q = q.Arg("mediaTypes", opts[i].MediaTypes) + } + } + + return &File{ + query: q, + } +} + +// The combined buffered standard output and standard error stream of the last executed command +// +// Returns an error if no command was executed +func (r *Container) CombinedOutput(ctx context.Context) (string, error) { + if r.combinedOutput != nil { + return *r.combinedOutput, nil + } + q := r.query.Select("combinedOutput") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Return the container's default arguments. +func (r *Container) DefaultArgs(ctx context.Context) ([]string, error) { + q := r.query.Select("defaultArgs") + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// ContainerDirectoryOpts contains options for Container.Directory +type ContainerDirectoryOpts struct { + // Replace "${VAR}" or "$VAR" in the value of path according to the current environment variables defined in the container (e.g. "/$VAR/foo"). + Expand bool +} + +// Retrieve a directory from the container's root filesystem +// +// Mounts are included. +func (r *Container) Directory(path string, opts ...ContainerDirectoryOpts) *Directory { + q := r.query.Select("directory") + for i := len(opts) - 1; i >= 0; i-- { + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + q = q.Arg("path", path) + + return &Directory{ + query: q, + } +} + +// Retrieves this container's configured docker healthcheck. +func (r *Container) DockerHealthcheck(ctx context.Context) (*HealthcheckConfig, error) { + q := r.query.Select("dockerHealthcheck") + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &HealthcheckConfig{ + query: selectNode(q.Root(), *objectID, "HealthcheckConfig"), + }, nil +} + +// Return the container's OCI entrypoint. +func (r *Container) Entrypoint(ctx context.Context) ([]string, error) { + q := r.query.Select("entrypoint") + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Retrieves the value of the specified persistent environment variable. +func (r *Container) EnvVariable(ctx context.Context, name string) (string, error) { + if r.envVariable != nil { + return *r.envVariable, nil + } + q := r.query.Select("envVariable") + q = q.Arg("name", name) + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Retrieves the list of persistent environment variables configured on the container. +func (r *Container) EnvVariables(ctx context.Context) ([]EnvVariable, error) { + q := r.query.Select("envVariables") + + q = q.Select("id") + + type envVariables struct { + Id ID + } + + convert := func(fields []envVariables) []EnvVariable { + out := []EnvVariable{} + + for i := range fields { + val := EnvVariable{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "EnvVariable") + out = append(out, val) + } + + return out + } + var response []envVariables + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// ContainerExistsOpts contains options for Container.Exists +type ContainerExistsOpts struct { + // If specified, also validate the type of file (e.g. "REGULAR_TYPE", "DIRECTORY_TYPE", or "SYMLINK_TYPE"). + ExpectedType ExistsType + // If specified, do not follow symlinks. + DoNotFollowSymlinks bool + // Replace "${VAR}" or "$VAR" in the value of path according to the current environment variables defined in the container (e.g. "/$VAR/foo"). + Expand bool +} + +// check if a file or directory exists +func (r *Container) Exists(ctx context.Context, path string, opts ...ContainerExistsOpts) (bool, error) { + if r.exists != nil { + return *r.exists, nil + } + q := r.query.Select("exists") + for i := len(opts) - 1; i >= 0; i-- { + // `expectedType` optional argument + if !querybuilder.IsZeroValue(opts[i].ExpectedType) { + q = q.Arg("expectedType", opts[i].ExpectedType) + } + // `doNotFollowSymlinks` optional argument + if !querybuilder.IsZeroValue(opts[i].DoNotFollowSymlinks) { + q = q.Arg("doNotFollowSymlinks", opts[i].DoNotFollowSymlinks) + } + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + q = q.Arg("path", path) + + var response bool + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The exit code of the last executed command +// +// Returns an error if no command was executed +func (r *Container) ExitCode(ctx context.Context) (int, error) { + if r.exitCode != nil { + return *r.exitCode, nil + } + q := r.query.Select("exitCode") + + var response int + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// 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. +func (r *Container) ExperimentalWithAllGPUs() *Container { + q := r.query.Select("experimentalWithAllGPUs") + + return &Container{ + query: q, + } +} + +// 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. +func (r *Container) ExperimentalWithGPU(devices []string) *Container { + q := r.query.Select("experimentalWithGPU") + q = q.Arg("devices", devices) + + return &Container{ + query: q, + } +} + +// ContainerExportOpts contains options for Container.Export +type ContainerExportOpts struct { + // Identifiers for other platform specific containers. + // + // Used for multi-platform image. + PlatformVariants []*Container + // 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. + ForcedCompression ImageLayerCompression + // 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. + // + // Default: OCIMediaTypes + MediaTypes ImageMediaTypes + // Replace "${VAR}" or "$VAR" in the value of path according to the current environment variables defined in the container (e.g. "/$VAR/foo"). + Expand bool +} + +// Writes the container as an OCI tarball to the destination file path on the host. +// +// It can also export platform variants. +func (r *Container) Export(ctx context.Context, path string, opts ...ContainerExportOpts) (string, error) { + if r.export != nil { + return *r.export, nil + } + q := r.query.Select("export") + for i := len(opts) - 1; i >= 0; i-- { + // `platformVariants` optional argument + if !querybuilder.IsZeroValue(opts[i].PlatformVariants) { + q = q.Arg("platformVariants", opts[i].PlatformVariants) + } + // `forcedCompression` optional argument + if !querybuilder.IsZeroValue(opts[i].ForcedCompression) { + q = q.Arg("forcedCompression", opts[i].ForcedCompression) + } + // `mediaTypes` optional argument + if !querybuilder.IsZeroValue(opts[i].MediaTypes) { + q = q.Arg("mediaTypes", opts[i].MediaTypes) + } + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + q = q.Arg("path", path) + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// ContainerExportImageOpts contains options for Container.ExportImage +type ContainerExportImageOpts struct { + // Identifiers for other platform specific containers. + // + // Used for multi-platform image. + PlatformVariants []*Container + // 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. + ForcedCompression ImageLayerCompression + // 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. + // + // Default: OCIMediaTypes + MediaTypes ImageMediaTypes +} + +// Exports the container as an image to the host's container image store. +func (r *Container) ExportImage(ctx context.Context, name string, opts ...ContainerExportImageOpts) error { + if r.exportImage != nil { + return nil + } + q := r.query.Select("exportImage") + for i := len(opts) - 1; i >= 0; i-- { + // `platformVariants` optional argument + if !querybuilder.IsZeroValue(opts[i].PlatformVariants) { + q = q.Arg("platformVariants", opts[i].PlatformVariants) + } + // `forcedCompression` optional argument + if !querybuilder.IsZeroValue(opts[i].ForcedCompression) { + q = q.Arg("forcedCompression", opts[i].ForcedCompression) + } + // `mediaTypes` optional argument + if !querybuilder.IsZeroValue(opts[i].MediaTypes) { + q = q.Arg("mediaTypes", opts[i].MediaTypes) + } + } + q = q.Arg("name", name) + + return q.Execute(ctx) +} + +// Retrieves the list of exposed ports. +// +// This includes ports already exposed by the image, even if not explicitly added with dagger. +func (r *Container) ExposedPorts(ctx context.Context) ([]Port, error) { + q := r.query.Select("exposedPorts") + + q = q.Select("id") + + type exposedPorts struct { + Id ID + } + + convert := func(fields []exposedPorts) []Port { + out := []Port{} + + for i := range fields { + val := Port{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "Port") + out = append(out, val) + } + + return out + } + var response []exposedPorts + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// ContainerFileOpts contains options for Container.File +type ContainerFileOpts struct { + // Replace "${VAR}" or "$VAR" in the value of path according to the current environment variables defined in the container (e.g. "/$VAR/foo.txt"). + Expand bool +} + +// Retrieves a file at the given path. +// +// Mounts are included. +func (r *Container) File(path string, opts ...ContainerFileOpts) *File { + q := r.query.Select("file") + for i := len(opts) - 1; i >= 0; i-- { + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + q = q.Arg("path", path) + + return &File{ + query: q, + } +} + +// ContainerFromOpts contains options for Container.From +type ContainerFromOpts struct { + // Service to use as the registry endpoint for the image address. + // + // The service will be started only for this pull. + RegistryService *Service + // Protocol to use for registry communication. + // + // Defaults to "HTTPS". Use "HTTP" only for plain HTTP registries. + Protocol RegistryProtocol + // Allow HTTPS registry communication without verifying the server certificate. + InsecureSkipTLSVerify bool +} + +// Download a container image, and apply it to the container state. All previous state will be lost. +func (r *Container) From(address string, opts ...ContainerFromOpts) *Container { + q := r.query.Select("from") + for i := len(opts) - 1; i >= 0; i-- { + // `registryService` optional argument + if !querybuilder.IsZeroValue(opts[i].RegistryService) { + q = q.Arg("registryService", opts[i].RegistryService) + } + // `protocol` optional argument + if !querybuilder.IsZeroValue(opts[i].Protocol) { + q = q.Arg("protocol", opts[i].Protocol) + } + // `insecureSkipTLSVerify` optional argument + if !querybuilder.IsZeroValue(opts[i].InsecureSkipTLSVerify) { + q = q.Arg("insecureSkipTLSVerify", opts[i].InsecureSkipTLSVerify) + } + } + q = q.Arg("address", address) + + return &Container{ + query: q, + } +} + +// A unique identifier for this Container. +func (r *Container) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Container) XXX_GraphQLType() string { + return "Container" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Container) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Container) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Container) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Container) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = Container{query: selectNode(dag.query, id, "Container")} + return nil +} + +// The unique image reference which can only be retrieved immediately after the 'Container.From' call. +func (r *Container) ImageRef(ctx context.Context) (string, error) { + if r.imageRef != nil { + return *r.imageRef, nil + } + q := r.query.Select("imageRef") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// ContainerImportOpts contains options for Container.Import +type ContainerImportOpts struct { + // Identifies the tag to import from the archive, if the archive bundles multiple tags. + Tag string +} + +// Reads the container from an OCI tarball. +func (r *Container) Import(source *File, opts ...ContainerImportOpts) *Container { + assertNotNil("source", source) + q := r.query.Select("import") + for i := len(opts) - 1; i >= 0; i-- { + // `tag` optional argument + if !querybuilder.IsZeroValue(opts[i].Tag) { + q = q.Arg("tag", opts[i].Tag) + } + } + q = q.Arg("source", source) + + return &Container{ + query: q, + } +} + +// Retrieves the value of the specified label. +func (r *Container) Label(ctx context.Context, name string) (string, error) { + if r.label != nil { + return *r.label, nil + } + q := r.query.Select("label") + q = q.Arg("name", name) + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Retrieves the list of labels passed to container. +func (r *Container) Labels(ctx context.Context) ([]Label, error) { + q := r.query.Select("labels") + + q = q.Select("id") + + type labels struct { + Id ID + } + + convert := func(fields []labels) []Label { + out := []Label{} + + for i := range fields { + val := Label{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "Label") + out = append(out, val) + } + + return out + } + var response []labels + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// ContainerLayerOpts contains options for Container.Layer +type ContainerLayerOpts struct { + // 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. + ForcedCompression ImageLayerCompression + // Media types to use for image layers. Defaults to OCI. + // + // Default: OCIMediaTypes + MediaTypes ImageMediaTypes +} + +// Returns the image layer or configuration blob with the given digest as a File. +func (r *Container) Layer(id string, opts ...ContainerLayerOpts) *File { + q := r.query.Select("layer") + for i := len(opts) - 1; i >= 0; i-- { + // `forcedCompression` optional argument + if !querybuilder.IsZeroValue(opts[i].ForcedCompression) { + q = q.Arg("forcedCompression", opts[i].ForcedCompression) + } + // `mediaTypes` optional argument + if !querybuilder.IsZeroValue(opts[i].MediaTypes) { + q = q.Arg("mediaTypes", opts[i].MediaTypes) + } + } + q = q.Arg("id", id) + + return &File{ + query: q, + } +} + +// ContainerManifestOpts contains options for Container.Manifest +type ContainerManifestOpts struct { + // 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. + ForcedCompression ImageLayerCompression + // Media types to use for image layers. Defaults to OCI. + // + // Default: OCIMediaTypes + MediaTypes ImageMediaTypes +} + +// Computes and returns the manifest for this container as a File. +func (r *Container) Manifest(opts ...ContainerManifestOpts) *File { + q := r.query.Select("manifest") + for i := len(opts) - 1; i >= 0; i-- { + // `forcedCompression` optional argument + if !querybuilder.IsZeroValue(opts[i].ForcedCompression) { + q = q.Arg("forcedCompression", opts[i].ForcedCompression) + } + // `mediaTypes` optional argument + if !querybuilder.IsZeroValue(opts[i].MediaTypes) { + q = q.Arg("mediaTypes", opts[i].MediaTypes) + } + } + + return &File{ + query: q, + } +} + +// Retrieves the list of paths where a directory is mounted. +func (r *Container) Mounts(ctx context.Context) ([]string, error) { + q := r.query.Select("mounts") + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The platform this container executes and publishes as. +func (r *Container) Platform(ctx context.Context) (Platform, error) { + if r.platform != nil { + return *r.platform, nil + } + q := r.query.Select("platform") + + var response Platform + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// ContainerPublishOpts contains options for Container.Publish +type ContainerPublishOpts struct { + // Identifiers for other platform specific containers. + // + // Used for multi-platform image. + PlatformVariants []*Container + // 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. + ForcedCompression ImageLayerCompression + // 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. + // + // Default: OCIMediaTypes + MediaTypes ImageMediaTypes + // Service to use as the registry endpoint for the image address. + // + // The service will be started only for this push. + RegistryService *Service + // Protocol to use for registry communication. + // + // Defaults to "HTTPS". Use "HTTP" only for plain HTTP registries. + Protocol RegistryProtocol + // Allow HTTPS registry communication without verifying the server certificate. + InsecureSkipTLSVerify bool +} + +// 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 +func (r *Container) Publish(ctx context.Context, address string, opts ...ContainerPublishOpts) (string, error) { + if r.publish != nil { + return *r.publish, nil + } + q := r.query.Select("publish") + for i := len(opts) - 1; i >= 0; i-- { + // `platformVariants` optional argument + if !querybuilder.IsZeroValue(opts[i].PlatformVariants) { + q = q.Arg("platformVariants", opts[i].PlatformVariants) + } + // `forcedCompression` optional argument + if !querybuilder.IsZeroValue(opts[i].ForcedCompression) { + q = q.Arg("forcedCompression", opts[i].ForcedCompression) + } + // `mediaTypes` optional argument + if !querybuilder.IsZeroValue(opts[i].MediaTypes) { + q = q.Arg("mediaTypes", opts[i].MediaTypes) + } + // `registryService` optional argument + if !querybuilder.IsZeroValue(opts[i].RegistryService) { + q = q.Arg("registryService", opts[i].RegistryService) + } + // `protocol` optional argument + if !querybuilder.IsZeroValue(opts[i].Protocol) { + q = q.Arg("protocol", opts[i].Protocol) + } + // `insecureSkipTLSVerify` optional argument + if !querybuilder.IsZeroValue(opts[i].InsecureSkipTLSVerify) { + q = q.Arg("insecureSkipTLSVerify", opts[i].InsecureSkipTLSVerify) + } + } + q = q.Arg("address", address) + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// 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. +func (r *Container) Rootfs() *Directory { + q := r.query.Select("rootfs") + + return &Directory{ + query: q, + } +} + +// ContainerStatOpts contains options for Container.Stat +type ContainerStatOpts struct { + // If specified, do not follow symlinks. + DoNotFollowSymlinks bool +} + +// Return file status +func (r *Container) Stat(ctx context.Context, path string, opts ...ContainerStatOpts) (*Stat, error) { + q := r.query.Select("stat") + for i := len(opts) - 1; i >= 0; i-- { + // `doNotFollowSymlinks` optional argument + if !querybuilder.IsZeroValue(opts[i].DoNotFollowSymlinks) { + q = q.Arg("doNotFollowSymlinks", opts[i].DoNotFollowSymlinks) + } + } + q = q.Arg("path", path) + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &Stat{ + query: selectNode(q.Root(), *objectID, "Stat"), + }, nil +} + +// The buffered standard error stream of the last executed command +// +// Returns an error if no command was executed +func (r *Container) Stderr(ctx context.Context) (string, error) { + if r.stderr != nil { + return *r.stderr, nil + } + q := r.query.Select("stderr") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The buffered standard output stream of the last executed command +// +// Returns an error if no command was executed +func (r *Container) Stdout(ctx context.Context) (string, error) { + if r.stdout != nil { + return *r.stdout, nil + } + q := r.query.Select("stdout") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Forces evaluation of the pipeline in the engine. +// +// It doesn't run the default command if no exec has been set. +func (r *Container) Sync(ctx context.Context) (*Container, error) { + q := r.query.Select("sync") + + var id ID + if err := q.Bind(&id).Execute(ctx); err != nil { + return nil, err + } + return &Container{ + query: selectNode(q.Root(), id, "Container"), + }, nil +} + +// ContainerTerminalOpts contains options for Container.Terminal +type ContainerTerminalOpts struct { + // If set, override the container's default terminal command and invoke these command arguments instead. + Cmd []string + // Provides Dagger access to the executed command. + ExperimentalPrivilegedNesting bool + // 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. + InsecureRootCapabilities bool +} + +// Opens an interactive terminal for this container using its configured default terminal command if not overridden by args (or sh as a fallback default). +func (r *Container) Terminal(opts ...ContainerTerminalOpts) *Container { + q := r.query.Select("terminal") + for i := len(opts) - 1; i >= 0; i-- { + // `cmd` optional argument + if !querybuilder.IsZeroValue(opts[i].Cmd) { + q = q.Arg("cmd", opts[i].Cmd) + } + // `experimentalPrivilegedNesting` optional argument + if !querybuilder.IsZeroValue(opts[i].ExperimentalPrivilegedNesting) { + q = q.Arg("experimentalPrivilegedNesting", opts[i].ExperimentalPrivilegedNesting) + } + // `insecureRootCapabilities` optional argument + if !querybuilder.IsZeroValue(opts[i].InsecureRootCapabilities) { + q = q.Arg("insecureRootCapabilities", opts[i].InsecureRootCapabilities) + } + } + + return &Container{ + query: q, + } +} + +// ContainerUpOpts contains options for Container.Up +type ContainerUpOpts struct { + // Bind each tunnel port to a random port on the host. + Random bool + // List of frontend/backend port mappings to forward. + // + // Frontend is the port accepting traffic on the host, backend is the service port. + Ports []PortForward + // 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. + Args []string + // If the container has an entrypoint, prepend it to the args. + UseEntrypoint bool + // Provides Dagger access to the executed command. + ExperimentalPrivilegedNesting bool + // 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. + InsecureRootCapabilities bool + // Replace "${VAR}" or "$VAR" in the args according to the current environment variables defined in the container (e.g. "/$VAR/foo"). + Expand bool + // 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. + NoInit bool +} + +// 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. +func (r *Container) Up(ctx context.Context, opts ...ContainerUpOpts) error { + if r.up != nil { + return nil + } + q := r.query.Select("up") + for i := len(opts) - 1; i >= 0; i-- { + // `random` optional argument + if !querybuilder.IsZeroValue(opts[i].Random) { + q = q.Arg("random", opts[i].Random) + } + // `ports` optional argument + if !querybuilder.IsZeroValue(opts[i].Ports) { + q = q.Arg("ports", opts[i].Ports) + } + // `args` optional argument + if !querybuilder.IsZeroValue(opts[i].Args) { + q = q.Arg("args", opts[i].Args) + } + // `useEntrypoint` optional argument + if !querybuilder.IsZeroValue(opts[i].UseEntrypoint) { + q = q.Arg("useEntrypoint", opts[i].UseEntrypoint) + } + // `experimentalPrivilegedNesting` optional argument + if !querybuilder.IsZeroValue(opts[i].ExperimentalPrivilegedNesting) { + q = q.Arg("experimentalPrivilegedNesting", opts[i].ExperimentalPrivilegedNesting) + } + // `insecureRootCapabilities` optional argument + if !querybuilder.IsZeroValue(opts[i].InsecureRootCapabilities) { + q = q.Arg("insecureRootCapabilities", opts[i].InsecureRootCapabilities) + } + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + // `noInit` optional argument + if !querybuilder.IsZeroValue(opts[i].NoInit) { + q = q.Arg("noInit", opts[i].NoInit) + } + } + + return q.Execute(ctx) +} + +// Retrieves the user to be set for all commands. +func (r *Container) User(ctx context.Context) (string, error) { + if r.user != nil { + return *r.user, nil + } + q := r.query.Select("user") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Retrieves this container plus the given OCI annotation. +func (r *Container) WithAnnotation(name string, value string) *Container { + q := r.query.Select("withAnnotation") + q = q.Arg("name", name) + q = q.Arg("value", value) + + return &Container{ + query: q, + } +} + +// Configures default arguments for future commands. Like CMD in Dockerfile. +func (r *Container) WithDefaultArgs(args []string) *Container { + q := r.query.Select("withDefaultArgs") + q = q.Arg("args", args) + + return &Container{ + query: q, + } +} + +// ContainerWithDefaultTerminalCmdOpts contains options for Container.WithDefaultTerminalCmd +type ContainerWithDefaultTerminalCmdOpts struct { + // Provides Dagger access to the executed command. + ExperimentalPrivilegedNesting bool + // 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. + InsecureRootCapabilities bool +} + +// Set the default command to invoke for the container's terminal API. +func (r *Container) WithDefaultTerminalCmd(args []string, opts ...ContainerWithDefaultTerminalCmdOpts) *Container { + q := r.query.Select("withDefaultTerminalCmd") + for i := len(opts) - 1; i >= 0; i-- { + // `experimentalPrivilegedNesting` optional argument + if !querybuilder.IsZeroValue(opts[i].ExperimentalPrivilegedNesting) { + q = q.Arg("experimentalPrivilegedNesting", opts[i].ExperimentalPrivilegedNesting) + } + // `insecureRootCapabilities` optional argument + if !querybuilder.IsZeroValue(opts[i].InsecureRootCapabilities) { + q = q.Arg("insecureRootCapabilities", opts[i].InsecureRootCapabilities) + } + } + q = q.Arg("args", args) + + return &Container{ + query: q, + } +} + +// ContainerWithDirectoryOpts contains options for Container.WithDirectory +type ContainerWithDirectoryOpts struct { + // Patterns to exclude in the written directory (e.g. ["node_modules/**", ".gitignore", ".git/"]). + Exclude []string + // Patterns to include in the written directory (e.g. ["*.go", "go.mod", "go.sum"]). + Include []string + // Apply .gitignore rules when writing the directory. + Gitignore bool + // 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. + Owner string + // Set the owner to the container's current user. + InheritOwner bool + // Replace "${VAR}" or "$VAR" in the value of path according to the current environment variables defined in the container (e.g. "/$VAR/foo"). + Expand bool + + Permissions int +} + +// Return a new container snapshot, with a directory added to its filesystem +func (r *Container) WithDirectory(path string, source *Directory, opts ...ContainerWithDirectoryOpts) *Container { + assertNotNil("source", source) + q := r.query.Select("withDirectory") + for i := len(opts) - 1; i >= 0; i-- { + // `exclude` optional argument + if !querybuilder.IsZeroValue(opts[i].Exclude) { + q = q.Arg("exclude", opts[i].Exclude) + } + // `include` optional argument + if !querybuilder.IsZeroValue(opts[i].Include) { + q = q.Arg("include", opts[i].Include) + } + // `gitignore` optional argument + if !querybuilder.IsZeroValue(opts[i].Gitignore) { + q = q.Arg("gitignore", opts[i].Gitignore) + } + // `owner` optional argument + if !querybuilder.IsZeroValue(opts[i].Owner) { + q = q.Arg("owner", opts[i].Owner) + } + // `inheritOwner` optional argument + if !querybuilder.IsZeroValue(opts[i].InheritOwner) { + q = q.Arg("inheritOwner", opts[i].InheritOwner) + } + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + // `permissions` optional argument + if !querybuilder.IsZeroValue(opts[i].Permissions) { + q = q.Arg("permissions", opts[i].Permissions) + } + } + q = q.Arg("path", path) + q = q.Arg("source", source) + + return &Container{ + query: q, + } +} + +// ContainerWithDockerHealthcheckOpts contains options for Container.WithDockerHealthcheck +type ContainerWithDockerHealthcheckOpts struct { + // When true, command must be a single element, which is run using the container's shell + Shell bool + // Interval between running healthcheck. Example: "30s" + Interval string + // Healthcheck timeout. Example: "3s" + Timeout string + // StartPeriod allows for failures during this initial startup period which do not count towards maximum number of retries. Example: "0s" + StartPeriod string + // StartInterval configures the duration between checks during the startup phase. Example: "5s" + StartInterval string + // The maximum number of consecutive failures before the container is marked as unhealthy. Example: "3" + Retries int +} + +// Retrieves this container with the specificed docker healtcheck command set. +func (r *Container) WithDockerHealthcheck(args []string, opts ...ContainerWithDockerHealthcheckOpts) *Container { + q := r.query.Select("withDockerHealthcheck") + for i := len(opts) - 1; i >= 0; i-- { + // `shell` optional argument + if !querybuilder.IsZeroValue(opts[i].Shell) { + q = q.Arg("shell", opts[i].Shell) + } + // `interval` optional argument + if !querybuilder.IsZeroValue(opts[i].Interval) { + q = q.Arg("interval", opts[i].Interval) + } + // `timeout` optional argument + if !querybuilder.IsZeroValue(opts[i].Timeout) { + q = q.Arg("timeout", opts[i].Timeout) + } + // `startPeriod` optional argument + if !querybuilder.IsZeroValue(opts[i].StartPeriod) { + q = q.Arg("startPeriod", opts[i].StartPeriod) + } + // `startInterval` optional argument + if !querybuilder.IsZeroValue(opts[i].StartInterval) { + q = q.Arg("startInterval", opts[i].StartInterval) + } + // `retries` optional argument + if !querybuilder.IsZeroValue(opts[i].Retries) { + q = q.Arg("retries", opts[i].Retries) + } + } + q = q.Arg("args", args) + + return &Container{ + query: q, + } +} + +// ContainerWithEntrypointOpts contains options for Container.WithEntrypoint +type ContainerWithEntrypointOpts struct { + // Don't reset the default arguments when setting the entrypoint. By default it is reset, since entrypoint and default args are often tightly coupled. + KeepDefaultArgs bool +} + +// Set an OCI-style entrypoint. It will be included in the container's OCI configuration. Note, withExec ignores the entrypoint by default. +func (r *Container) WithEntrypoint(args []string, opts ...ContainerWithEntrypointOpts) *Container { + q := r.query.Select("withEntrypoint") + for i := len(opts) - 1; i >= 0; i-- { + // `keepDefaultArgs` optional argument + if !querybuilder.IsZeroValue(opts[i].KeepDefaultArgs) { + q = q.Arg("keepDefaultArgs", opts[i].KeepDefaultArgs) + } + } + q = q.Arg("args", args) + + return &Container{ + query: q, + } +} + +// Export environment variables from an env-file to the container. +func (r *Container) WithEnvFileVariables(source *EnvFile) *Container { + assertNotNil("source", source) + q := r.query.Select("withEnvFileVariables") + q = q.Arg("source", source) + + return &Container{ + query: q, + } +} + +// ContainerWithEnvVariableOpts contains options for Container.WithEnvVariable +type ContainerWithEnvVariableOpts struct { + // Replace "${VAR}" or "$VAR" in the value according to the current environment variables defined in the container (e.g. "/opt/bin:$PATH"). + Expand bool +} + +// Set a new environment variable in the container. +func (r *Container) WithEnvVariable(name string, value string, opts ...ContainerWithEnvVariableOpts) *Container { + q := r.query.Select("withEnvVariable") + for i := len(opts) - 1; i >= 0; i-- { + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + q = q.Arg("name", name) + q = q.Arg("value", value) + + return &Container{ + query: q, + } +} + +// Raise an error. +func (r *Container) WithError(err string) *Container { + q := r.query.Select("withError") + q = q.Arg("err", err) + + return &Container{ + query: q, + } +} + +// ContainerWithExecOpts contains options for Container.WithExec +type ContainerWithExecOpts struct { + // Apply the OCI entrypoint, if present, by prepending it to the args. Ignored by default. + UseEntrypoint bool + // Content to write to the command's standard input. Example: "Hello world") + Stdin string + // Redirect the command's standard input from a file in the container. Example: "./stdin.txt" + RedirectStdin string + // Redirect the command's standard output to a file in the container. Example: "./stdout.txt" + RedirectStdout string + // Redirect the command's standard error to a file in the container. Example: "./stderr.txt" + RedirectStderr string + // Exit codes this command is allowed to exit with without error + // + // Default: SUCCESS + Expect ReturnType + // Provides Dagger access to the executed command. + ExperimentalPrivilegedNesting bool + // 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. + InsecureRootCapabilities bool + // Replace "${VAR}" or "$VAR" in the args according to the current environment variables defined in the container (e.g. "/$VAR/foo"). + Expand bool + // 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. + NoInit bool +} + +// Execute a command in the container, and return a new snapshot of the container state after execution. +func (r *Container) WithExec(args []string, opts ...ContainerWithExecOpts) *Container { + q := r.query.Select("withExec") + for i := len(opts) - 1; i >= 0; i-- { + // `useEntrypoint` optional argument + if !querybuilder.IsZeroValue(opts[i].UseEntrypoint) { + q = q.Arg("useEntrypoint", opts[i].UseEntrypoint) + } + // `stdin` optional argument + if !querybuilder.IsZeroValue(opts[i].Stdin) { + q = q.Arg("stdin", opts[i].Stdin) + } + // `redirectStdin` optional argument + if !querybuilder.IsZeroValue(opts[i].RedirectStdin) { + q = q.Arg("redirectStdin", opts[i].RedirectStdin) + } + // `redirectStdout` optional argument + if !querybuilder.IsZeroValue(opts[i].RedirectStdout) { + q = q.Arg("redirectStdout", opts[i].RedirectStdout) + } + // `redirectStderr` optional argument + if !querybuilder.IsZeroValue(opts[i].RedirectStderr) { + q = q.Arg("redirectStderr", opts[i].RedirectStderr) + } + // `expect` optional argument + if !querybuilder.IsZeroValue(opts[i].Expect) { + q = q.Arg("expect", opts[i].Expect) + } + // `experimentalPrivilegedNesting` optional argument + if !querybuilder.IsZeroValue(opts[i].ExperimentalPrivilegedNesting) { + q = q.Arg("experimentalPrivilegedNesting", opts[i].ExperimentalPrivilegedNesting) + } + // `insecureRootCapabilities` optional argument + if !querybuilder.IsZeroValue(opts[i].InsecureRootCapabilities) { + q = q.Arg("insecureRootCapabilities", opts[i].InsecureRootCapabilities) + } + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + // `noInit` optional argument + if !querybuilder.IsZeroValue(opts[i].NoInit) { + q = q.Arg("noInit", opts[i].NoInit) + } + } + q = q.Arg("args", args) + + return &Container{ + query: q, + } +} + +// ContainerWithExposedPortOpts contains options for Container.WithExposedPort +type ContainerWithExposedPortOpts struct { + // Network protocol. Example: "tcp" + // + // Default: TCP + Protocol NetworkProtocol + // Port description. Example: "payment API endpoint" + Description string + // Skip the health check when run as a service. + ExperimentalSkipHealthcheck bool +} + +// 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 +func (r *Container) WithExposedPort(port int, opts ...ContainerWithExposedPortOpts) *Container { + q := r.query.Select("withExposedPort") + for i := len(opts) - 1; i >= 0; i-- { + // `protocol` optional argument + if !querybuilder.IsZeroValue(opts[i].Protocol) { + q = q.Arg("protocol", opts[i].Protocol) + } + // `description` optional argument + if !querybuilder.IsZeroValue(opts[i].Description) { + q = q.Arg("description", opts[i].Description) + } + // `experimentalSkipHealthcheck` optional argument + if !querybuilder.IsZeroValue(opts[i].ExperimentalSkipHealthcheck) { + q = q.Arg("experimentalSkipHealthcheck", opts[i].ExperimentalSkipHealthcheck) + } + } + q = q.Arg("port", port) + + return &Container{ + query: q, + } +} + +// ContainerWithFileOpts contains options for Container.WithFile +type ContainerWithFileOpts struct { + // Permissions of the new file. Example: 0600 + Permissions int + // 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. + Owner string + // Set the owner to the container's current user. + InheritOwner bool + // Replace "${VAR}" or "$VAR" in the value of path according to the current environment variables defined in the container (e.g. "/$VAR/foo.txt"). + Expand bool +} + +// Return a container snapshot with a file added +func (r *Container) WithFile(path string, source *File, opts ...ContainerWithFileOpts) *Container { + assertNotNil("source", source) + q := r.query.Select("withFile") + for i := len(opts) - 1; i >= 0; i-- { + // `permissions` optional argument + if !querybuilder.IsZeroValue(opts[i].Permissions) { + q = q.Arg("permissions", opts[i].Permissions) + } + // `owner` optional argument + if !querybuilder.IsZeroValue(opts[i].Owner) { + q = q.Arg("owner", opts[i].Owner) + } + // `inheritOwner` optional argument + if !querybuilder.IsZeroValue(opts[i].InheritOwner) { + q = q.Arg("inheritOwner", opts[i].InheritOwner) + } + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + q = q.Arg("path", path) + q = q.Arg("source", source) + + return &Container{ + query: q, + } +} + +// ContainerWithFilesOpts contains options for Container.WithFiles +type ContainerWithFilesOpts struct { + // Permission given to the copied files (e.g., 0600). + Permissions int + // 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. + Owner string + // Set the owner to the container's current user. + InheritOwner bool + // Replace "${VAR}" or "$VAR" in the value of path according to the current environment variables defined in the container (e.g. "/$VAR/foo.txt"). + Expand bool +} + +// Retrieves this container plus the contents of the given files copied to the given path. +func (r *Container) WithFiles(path string, sources []*File, opts ...ContainerWithFilesOpts) *Container { + q := r.query.Select("withFiles") + for i := len(opts) - 1; i >= 0; i-- { + // `permissions` optional argument + if !querybuilder.IsZeroValue(opts[i].Permissions) { + q = q.Arg("permissions", opts[i].Permissions) + } + // `owner` optional argument + if !querybuilder.IsZeroValue(opts[i].Owner) { + q = q.Arg("owner", opts[i].Owner) + } + // `inheritOwner` optional argument + if !querybuilder.IsZeroValue(opts[i].InheritOwner) { + q = q.Arg("inheritOwner", opts[i].InheritOwner) + } + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + q = q.Arg("path", path) + q = q.Arg("sources", sources) + + return &Container{ + query: q, + } +} + +// Retrieves this container plus the given label. +func (r *Container) WithLabel(name string, value string) *Container { + q := r.query.Select("withLabel") + q = q.Arg("name", name) + q = q.Arg("value", value) + + return &Container{ + query: q, + } +} + +// ContainerWithMountedCacheOpts contains options for Container.WithMountedCache +type ContainerWithMountedCacheOpts struct { + // Identifier of the directory to use as the cache volume's root. + Source *Directory + // Sharing mode of the cache volume. + // + // Default: SHARED + Sharing CacheSharingMode + // 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. + Owner string + // Set the owner to the container's current user. + InheritOwner bool + // Replace "${VAR}" or "$VAR" in the value of path according to the current environment variables defined in the container (e.g. "/$VAR/foo"). + Expand bool +} + +// Retrieves this container plus a cache volume mounted at the given path. +func (r *Container) WithMountedCache(path string, cache *CacheVolume, opts ...ContainerWithMountedCacheOpts) *Container { + assertNotNil("cache", cache) + q := r.query.Select("withMountedCache") + for i := len(opts) - 1; i >= 0; i-- { + // `source` optional argument + if !querybuilder.IsZeroValue(opts[i].Source) { + q = q.Arg("source", opts[i].Source) + } + // `sharing` optional argument + if !querybuilder.IsZeroValue(opts[i].Sharing) { + q = q.Arg("sharing", opts[i].Sharing) + } + // `owner` optional argument + if !querybuilder.IsZeroValue(opts[i].Owner) { + q = q.Arg("owner", opts[i].Owner) + } + // `inheritOwner` optional argument + if !querybuilder.IsZeroValue(opts[i].InheritOwner) { + q = q.Arg("inheritOwner", opts[i].InheritOwner) + } + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + q = q.Arg("path", path) + q = q.Arg("cache", cache) + + return &Container{ + query: q, + } +} + +// ContainerWithMountedDirectoryOpts contains options for Container.WithMountedDirectory +type ContainerWithMountedDirectoryOpts struct { + // 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. + Owner string + // Set the owner to the container's current user. + InheritOwner bool + // Mount the directory read-only. + ReadOnly bool + // Replace "${VAR}" or "$VAR" in the value of path according to the current environment variables defined in the container (e.g. "/$VAR/foo"). + Expand bool +} + +// Retrieves this container plus a directory mounted at the given path. +func (r *Container) WithMountedDirectory(path string, source *Directory, opts ...ContainerWithMountedDirectoryOpts) *Container { + assertNotNil("source", source) + q := r.query.Select("withMountedDirectory") + for i := len(opts) - 1; i >= 0; i-- { + // `owner` optional argument + if !querybuilder.IsZeroValue(opts[i].Owner) { + q = q.Arg("owner", opts[i].Owner) + } + // `inheritOwner` optional argument + if !querybuilder.IsZeroValue(opts[i].InheritOwner) { + q = q.Arg("inheritOwner", opts[i].InheritOwner) + } + // `readOnly` optional argument + if !querybuilder.IsZeroValue(opts[i].ReadOnly) { + q = q.Arg("readOnly", opts[i].ReadOnly) + } + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + q = q.Arg("path", path) + q = q.Arg("source", source) + + return &Container{ + query: q, + } +} + +// ContainerWithMountedFileOpts contains options for Container.WithMountedFile +type ContainerWithMountedFileOpts struct { + // 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. + Owner string + // Set the owner to the container's current user. + InheritOwner bool + // Replace "${VAR}" or "$VAR" in the value of path according to the current environment variables defined in the container (e.g. "/$VAR/foo.txt"). + Expand bool +} + +// Retrieves this container plus a file mounted at the given path. +func (r *Container) WithMountedFile(path string, source *File, opts ...ContainerWithMountedFileOpts) *Container { + assertNotNil("source", source) + q := r.query.Select("withMountedFile") + for i := len(opts) - 1; i >= 0; i-- { + // `owner` optional argument + if !querybuilder.IsZeroValue(opts[i].Owner) { + q = q.Arg("owner", opts[i].Owner) + } + // `inheritOwner` optional argument + if !querybuilder.IsZeroValue(opts[i].InheritOwner) { + q = q.Arg("inheritOwner", opts[i].InheritOwner) + } + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + q = q.Arg("path", path) + q = q.Arg("source", source) + + return &Container{ + query: q, + } +} + +// ContainerWithMountedSecretOpts contains options for Container.WithMountedSecret +type ContainerWithMountedSecretOpts struct { + // 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. + Owner string + // Set the owner to the container's current user. + InheritOwner bool + // Permission given to the mounted secret (e.g., 0600). + // + // This option requires an owner to be set to be active. + // + // Default: 256 + Mode int + // Replace "${VAR}" or "$VAR" in the value of path according to the current environment variables defined in the container (e.g. "/$VAR/foo"). + Expand bool +} + +// Retrieves this container plus a secret mounted into a file at the given path. +func (r *Container) WithMountedSecret(path string, source *Secret, opts ...ContainerWithMountedSecretOpts) *Container { + assertNotNil("source", source) + q := r.query.Select("withMountedSecret") + for i := len(opts) - 1; i >= 0; i-- { + // `owner` optional argument + if !querybuilder.IsZeroValue(opts[i].Owner) { + q = q.Arg("owner", opts[i].Owner) + } + // `inheritOwner` optional argument + if !querybuilder.IsZeroValue(opts[i].InheritOwner) { + q = q.Arg("inheritOwner", opts[i].InheritOwner) + } + // `mode` optional argument + if !querybuilder.IsZeroValue(opts[i].Mode) { + q = q.Arg("mode", opts[i].Mode) + } + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + q = q.Arg("path", path) + q = q.Arg("source", source) + + return &Container{ + query: q, + } +} + +// ContainerWithMountedTempOpts contains options for Container.WithMountedTemp +type ContainerWithMountedTempOpts struct { + // Size of the temporary directory in bytes. + Size int + // Replace "${VAR}" or "$VAR" in the value of path according to the current environment variables defined in the container (e.g. "/$VAR/foo"). + Expand bool +} + +// 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. +func (r *Container) WithMountedTemp(path string, opts ...ContainerWithMountedTempOpts) *Container { + q := r.query.Select("withMountedTemp") + for i := len(opts) - 1; i >= 0; i-- { + // `size` optional argument + if !querybuilder.IsZeroValue(opts[i].Size) { + q = q.Arg("size", opts[i].Size) + } + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + q = q.Arg("path", path) + + return &Container{ + query: q, + } +} + +// ContainerWithMountedVolumeOpts contains options for Container.WithMountedVolume +type ContainerWithMountedVolumeOpts struct { + // Mount the volume read-only. + ReadOnly bool + // Replace "${VAR}" or "$VAR" in the value of path according to the current environment variables defined in the container (e.g. "/$VAR/foo"). + Expand bool +} + +// Retrieves this container plus a volume mounted at the given path. +func (r *Container) WithMountedVolume(path string, volume *Volume, opts ...ContainerWithMountedVolumeOpts) *Container { + assertNotNil("volume", volume) + q := r.query.Select("withMountedVolume") + for i := len(opts) - 1; i >= 0; i-- { + // `readOnly` optional argument + if !querybuilder.IsZeroValue(opts[i].ReadOnly) { + q = q.Arg("readOnly", opts[i].ReadOnly) + } + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + q = q.Arg("path", path) + q = q.Arg("volume", volume) + + return &Container{ + query: q, + } +} + +// ContainerWithNewFileOpts contains options for Container.WithNewFile +type ContainerWithNewFileOpts struct { + // Permissions of the new file. Example: 0600 + // + // Default: 420 + Permissions int + // 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. + Owner string + // Set the owner to the container's current user. + InheritOwner bool + // Replace "${VAR}" or "$VAR" in the value of path according to the current environment variables defined in the container (e.g. "/$VAR/foo.txt"). + Expand bool +} + +// Return a new container snapshot, with a file added to its filesystem with text content +func (r *Container) WithNewFile(path string, contents string, opts ...ContainerWithNewFileOpts) *Container { + q := r.query.Select("withNewFile") + for i := len(opts) - 1; i >= 0; i-- { + // `permissions` optional argument + if !querybuilder.IsZeroValue(opts[i].Permissions) { + q = q.Arg("permissions", opts[i].Permissions) + } + // `owner` optional argument + if !querybuilder.IsZeroValue(opts[i].Owner) { + q = q.Arg("owner", opts[i].Owner) + } + // `inheritOwner` optional argument + if !querybuilder.IsZeroValue(opts[i].InheritOwner) { + q = q.Arg("inheritOwner", opts[i].InheritOwner) + } + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + q = q.Arg("path", path) + q = q.Arg("contents", contents) + + return &Container{ + query: q, + } +} + +// Attach credentials for future publishing to a registry. Use in combination with publish +func (r *Container) WithRegistryAuth(address string, username string, secret *Secret) *Container { + assertNotNil("secret", secret) + q := r.query.Select("withRegistryAuth") + q = q.Arg("address", address) + q = q.Arg("username", username) + q = q.Arg("secret", secret) + + return &Container{ + query: q, + } +} + +// Change the container's root filesystem. The previous root filesystem will be lost. +func (r *Container) WithRootfs(directory *Directory) *Container { + assertNotNil("directory", directory) + q := r.query.Select("withRootfs") + q = q.Arg("directory", directory) + + return &Container{ + query: q, + } +} + +// Set a new environment variable, using a secret value +func (r *Container) WithSecretVariable(name string, secret *Secret) *Container { + assertNotNil("secret", secret) + q := r.query.Select("withSecretVariable") + q = q.Arg("name", name) + q = q.Arg("secret", secret) + + return &Container{ + query: q, + } +} + +// 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. +func (r *Container) WithServiceBinding(alias string, service *Service) *Container { + assertNotNil("service", service) + q := r.query.Select("withServiceBinding") + q = q.Arg("alias", alias) + q = q.Arg("service", service) + + return &Container{ + query: q, + } +} + +// ContainerWithSymlinkOpts contains options for Container.WithSymlink +type ContainerWithSymlinkOpts struct { + // Replace "${VAR}" or "$VAR" in the value of path according to the current environment variables defined in the container (e.g. "/$VAR/foo.txt"). + Expand bool +} + +// Return a snapshot with a symlink +func (r *Container) WithSymlink(target string, linkName string, opts ...ContainerWithSymlinkOpts) *Container { + q := r.query.Select("withSymlink") + for i := len(opts) - 1; i >= 0; i-- { + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + q = q.Arg("target", target) + q = q.Arg("linkName", linkName) + + return &Container{ + query: q, + } +} + +// ContainerWithUnixSocketOpts contains options for Container.WithUnixSocket +type ContainerWithUnixSocketOpts struct { + // 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. + Owner string + // Set the owner to the container's current user. + InheritOwner bool + // Replace "${VAR}" or "$VAR" in the value of path according to the current environment variables defined in the container (e.g. "/$VAR/foo"). + Expand bool +} + +// Retrieves this container plus a socket forwarded to the given Unix socket path. +func (r *Container) WithUnixSocket(path string, source *Socket, opts ...ContainerWithUnixSocketOpts) *Container { + assertNotNil("source", source) + q := r.query.Select("withUnixSocket") + for i := len(opts) - 1; i >= 0; i-- { + // `owner` optional argument + if !querybuilder.IsZeroValue(opts[i].Owner) { + q = q.Arg("owner", opts[i].Owner) + } + // `inheritOwner` optional argument + if !querybuilder.IsZeroValue(opts[i].InheritOwner) { + q = q.Arg("inheritOwner", opts[i].InheritOwner) + } + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + q = q.Arg("path", path) + q = q.Arg("source", source) + + return &Container{ + query: q, + } +} + +// Retrieves this container with a different command user. +func (r *Container) WithUser(name string) *Container { + q := r.query.Select("withUser") + q = q.Arg("name", name) + + return &Container{ + query: q, + } +} + +// Set a new non-secret environment variable for future execs without invalidating exec cache when only its value changes. +// +// This is an expert-only escape hatch. If a volatile value affects observable exec results, stale cached results may be reused. +func (r *Container) WithVolatileVariable(name string, value string) *Container { + q := r.query.Select("withVolatileVariable") + q = q.Arg("name", name) + q = q.Arg("value", value) + + return &Container{ + query: q, + } +} + +// ContainerWithWorkdirOpts contains options for Container.WithWorkdir +type ContainerWithWorkdirOpts struct { + // Replace "${VAR}" or "$VAR" in the value of path according to the current environment variables defined in the container (e.g. "/$VAR/foo"). + Expand bool +} + +// Change the container's working directory. Like WORKDIR in Dockerfile. +func (r *Container) WithWorkdir(path string, opts ...ContainerWithWorkdirOpts) *Container { + q := r.query.Select("withWorkdir") + for i := len(opts) - 1; i >= 0; i-- { + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + q = q.Arg("path", path) + + return &Container{ + query: q, + } +} + +// Retrieves this container minus the given OCI annotation. +func (r *Container) WithoutAnnotation(name string) *Container { + q := r.query.Select("withoutAnnotation") + q = q.Arg("name", name) + + return &Container{ + query: q, + } +} + +// Remove the container's default arguments. +func (r *Container) WithoutDefaultArgs() *Container { + q := r.query.Select("withoutDefaultArgs") + + return &Container{ + query: q, + } +} + +// ContainerWithoutDirectoryOpts contains options for Container.WithoutDirectory +type ContainerWithoutDirectoryOpts struct { + // Replace "${VAR}" or "$VAR" in the value of path according to the current environment variables defined in the container (e.g. "/$VAR/foo"). + Expand bool +} + +// Return a new container snapshot, with a directory removed from its filesystem +func (r *Container) WithoutDirectory(path string, opts ...ContainerWithoutDirectoryOpts) *Container { + q := r.query.Select("withoutDirectory") + for i := len(opts) - 1; i >= 0; i-- { + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + q = q.Arg("path", path) + + return &Container{ + query: q, + } +} + +// Retrieves this container without a configured docker healtcheck command. +func (r *Container) WithoutDockerHealthcheck() *Container { + q := r.query.Select("withoutDockerHealthcheck") + + return &Container{ + query: q, + } +} + +// ContainerWithoutEntrypointOpts contains options for Container.WithoutEntrypoint +type ContainerWithoutEntrypointOpts struct { + // Don't remove the default arguments when unsetting the entrypoint. + KeepDefaultArgs bool +} + +// Reset the container's OCI entrypoint. +func (r *Container) WithoutEntrypoint(opts ...ContainerWithoutEntrypointOpts) *Container { + q := r.query.Select("withoutEntrypoint") + for i := len(opts) - 1; i >= 0; i-- { + // `keepDefaultArgs` optional argument + if !querybuilder.IsZeroValue(opts[i].KeepDefaultArgs) { + q = q.Arg("keepDefaultArgs", opts[i].KeepDefaultArgs) + } + } + + return &Container{ + query: q, + } +} + +// Retrieves this container minus the given environment variable. +func (r *Container) WithoutEnvVariable(name string) *Container { + q := r.query.Select("withoutEnvVariable") + q = q.Arg("name", name) + + return &Container{ + query: q, + } +} + +// ContainerWithoutExposedPortOpts contains options for Container.WithoutExposedPort +type ContainerWithoutExposedPortOpts struct { + // Port protocol to unexpose + // + // Default: TCP + Protocol NetworkProtocol +} + +// Unexpose a previously exposed port. +func (r *Container) WithoutExposedPort(port int, opts ...ContainerWithoutExposedPortOpts) *Container { + q := r.query.Select("withoutExposedPort") + for i := len(opts) - 1; i >= 0; i-- { + // `protocol` optional argument + if !querybuilder.IsZeroValue(opts[i].Protocol) { + q = q.Arg("protocol", opts[i].Protocol) + } + } + q = q.Arg("port", port) + + return &Container{ + query: q, + } +} + +// ContainerWithoutFileOpts contains options for Container.WithoutFile +type ContainerWithoutFileOpts struct { + // Replace "${VAR}" or "$VAR" in the value of path according to the current environment variables defined in the container (e.g. "/$VAR/foo.txt"). + Expand bool +} + +// Retrieves this container with the file at the given path removed. +func (r *Container) WithoutFile(path string, opts ...ContainerWithoutFileOpts) *Container { + q := r.query.Select("withoutFile") + for i := len(opts) - 1; i >= 0; i-- { + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + q = q.Arg("path", path) + + return &Container{ + query: q, + } +} + +// ContainerWithoutFilesOpts contains options for Container.WithoutFiles +type ContainerWithoutFilesOpts struct { + // Replace "${VAR}" or "$VAR" in the value of paths according to the current environment variables defined in the container (e.g. "/$VAR/foo.txt"). + Expand bool +} + +// Return a new container spanshot with specified files removed +func (r *Container) WithoutFiles(paths []string, opts ...ContainerWithoutFilesOpts) *Container { + q := r.query.Select("withoutFiles") + for i := len(opts) - 1; i >= 0; i-- { + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + q = q.Arg("paths", paths) + + return &Container{ + query: q, + } +} + +// Retrieves this container minus the given environment label. +func (r *Container) WithoutLabel(name string) *Container { + q := r.query.Select("withoutLabel") + q = q.Arg("name", name) + + return &Container{ + query: q, + } +} + +// ContainerWithoutMountOpts contains options for Container.WithoutMount +type ContainerWithoutMountOpts struct { + // Replace "${VAR}" or "$VAR" in the value of path according to the current environment variables defined in the container (e.g. "/$VAR/foo"). + Expand bool +} + +// Retrieves this container after unmounting everything at the given path. +func (r *Container) WithoutMount(path string, opts ...ContainerWithoutMountOpts) *Container { + q := r.query.Select("withoutMount") + for i := len(opts) - 1; i >= 0; i-- { + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + q = q.Arg("path", path) + + return &Container{ + query: q, + } +} + +// Retrieves this container without the registry authentication of a given address. +func (r *Container) WithoutRegistryAuth(address string) *Container { + q := r.query.Select("withoutRegistryAuth") + q = q.Arg("address", address) + + return &Container{ + query: q, + } +} + +// Retrieves this container minus the given environment variable containing the secret. +func (r *Container) WithoutSecretVariable(name string) *Container { + q := r.query.Select("withoutSecretVariable") + q = q.Arg("name", name) + + return &Container{ + query: q, + } +} + +// ContainerWithoutUnixSocketOpts contains options for Container.WithoutUnixSocket +type ContainerWithoutUnixSocketOpts struct { + // Replace "${VAR}" or "$VAR" in the value of path according to the current environment variables defined in the container (e.g. "/$VAR/foo"). + Expand bool +} + +// Retrieves this container with a previously added Unix socket removed. +func (r *Container) WithoutUnixSocket(path string, opts ...ContainerWithoutUnixSocketOpts) *Container { + q := r.query.Select("withoutUnixSocket") + for i := len(opts) - 1; i >= 0; i-- { + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + q = q.Arg("path", path) + + return &Container{ + query: q, + } +} + +// Retrieves this container with an unset command user. +// +// Should default to root. +func (r *Container) WithoutUser() *Container { + q := r.query.Select("withoutUser") + + return &Container{ + query: q, + } +} + +// Retrieves this container minus the given volatile environment variable. +func (r *Container) WithoutVolatileVariable(name string) *Container { + q := r.query.Select("withoutVolatileVariable") + q = q.Arg("name", name) + + return &Container{ + query: q, + } +} + +// Unset the container's working directory. +// +// Should default to "/". +func (r *Container) WithoutWorkdir() *Container { + q := r.query.Select("withoutWorkdir") + + return &Container{ + query: q, + } +} + +// Retrieves the working directory for all commands. +func (r *Container) Workdir(ctx context.Context) (string, error) { + if r.workdir != nil { + return *r.workdir, nil + } + q := r.query.Select("workdir") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsExportable returns this Container as a Exportable. +// This is a local type conversion — no GraphQL call. +func (r *Container) AsExportable() Exportable { + return &ExportableClient{ + query: r.query, + } +} + +// AsNode returns this Container as a Node. +// This is a local type conversion — no GraphQL call. +func (r *Container) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// AsSyncer returns this Container as a Syncer. +// This is a local type conversion — no GraphQL call. +func (r *Container) AsSyncer() Syncer { + return &SyncerClient{ + query: r.query, + } +} + +// Reflective module API provided to functions at runtime. +type CurrentModule struct { + query *querybuilder.Selection + + id *ID + name *string +} + +func (r *CurrentModule) WithGraphQLQuery(q *querybuilder.Selection) *CurrentModule { + return &CurrentModule{ + query: q, + } +} + +// Treat the currently executing module as an SDK installed in the given workspace, exposing the modules and clients it manages. +// +// Errors if the current module is not installed as an SDK in this workspace. +func (r *CurrentModule) AsSDK(workspace *Workspace) *CurrentModuleAsSDK { + assertNotNil("workspace", workspace) + q := r.query.Select("asSDK") + q = q.Arg("workspace", workspace) + + return &CurrentModuleAsSDK{ + query: q, + } +} + +// The dependencies of the module. +func (r *CurrentModule) Dependencies(ctx context.Context) ([]Module, error) { + q := r.query.Select("dependencies") + + q = q.Select("id") + + type dependencies struct { + Id ID + } + + convert := func(fields []dependencies) []Module { + out := []Module{} + + for i := range fields { + val := Module{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "Module") + out = append(out, val) + } + + return out + } + var response []dependencies + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// The generated files and directories made on top of the module source's context directory. +func (r *CurrentModule) GeneratedContextDirectory() *Directory { + q := r.query.Select("generatedContextDirectory") + + return &Directory{ + query: q, + } +} + +// CurrentModuleGeneratorsOpts contains options for CurrentModule.Generators +type CurrentModuleGeneratorsOpts struct { + // Only include generators matching the specified patterns + Include []string +} + +// Return all generators defined by the module +// +// Experimental: This API is highly experimental and may be removed or replaced entirely. +func (r *CurrentModule) Generators(opts ...CurrentModuleGeneratorsOpts) *GeneratorGroup { + q := r.query.Select("generators") + for i := len(opts) - 1; i >= 0; i-- { + // `include` optional argument + if !querybuilder.IsZeroValue(opts[i].Include) { + q = q.Arg("include", opts[i].Include) + } + } + + return &GeneratorGroup{ + query: q, + } +} + +// A unique identifier for this CurrentModule. +func (r *CurrentModule) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *CurrentModule) XXX_GraphQLType() string { + return "CurrentModule" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *CurrentModule) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *CurrentModule) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *CurrentModule) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *CurrentModule) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = CurrentModule{query: selectNode(dag.query, id, "CurrentModule")} + return nil +} + +// The name of the module being executed in +func (r *CurrentModule) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The directory containing the module's source code loaded into the engine (plus any generated code that may have been created). +func (r *CurrentModule) Source() *Directory { + q := r.query.Select("source") + + return &Directory{ + query: q, + } +} + +// CurrentModuleWorkdirOpts contains options for CurrentModule.Workdir +type CurrentModuleWorkdirOpts struct { + // Exclude artifacts that match the given pattern (e.g., ["node_modules/", ".git*"]). + Exclude []string + // Include only artifacts that match the given pattern (e.g., ["app/", "package.*"]). + Include []string + // Apply .gitignore filter rules inside the directory + Gitignore bool +} + +// Load a directory from the module's scratch working directory, including any changes that may have been made to it during module function execution. +func (r *CurrentModule) Workdir(path string, opts ...CurrentModuleWorkdirOpts) *Directory { + q := r.query.Select("workdir") + for i := len(opts) - 1; i >= 0; i-- { + // `exclude` optional argument + if !querybuilder.IsZeroValue(opts[i].Exclude) { + q = q.Arg("exclude", opts[i].Exclude) + } + // `include` optional argument + if !querybuilder.IsZeroValue(opts[i].Include) { + q = q.Arg("include", opts[i].Include) + } + // `gitignore` optional argument + if !querybuilder.IsZeroValue(opts[i].Gitignore) { + q = q.Arg("gitignore", opts[i].Gitignore) + } + } + q = q.Arg("path", path) + + return &Directory{ + query: q, + } +} + +// 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. +func (r *CurrentModule) WorkdirFile(path string) *File { + q := r.query.Select("workdirFile") + q = q.Arg("path", path) + + return &File{ + query: q, + } +} + +// AsNode returns this CurrentModule as a Node. +// This is a local type conversion — no GraphQL call. +func (r *CurrentModule) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// The SDK-role data for the currently executing module, as installed in the supplied workspace. +type CurrentModuleAsSDK struct { + query *querybuilder.Selection + + id *ID + name *string +} + +func (r *CurrentModuleAsSDK) WithGraphQLQuery(q *querybuilder.Selection) *CurrentModuleAsSDK { + return &CurrentModuleAsSDK{ + query: q, + } +} + +// The generated clients this SDK produces in the workspace. +func (r *CurrentModuleAsSDK) Clients(ctx context.Context) ([]CurrentModuleAsSDKClient, error) { + q := r.query.Select("clients") + + q = q.Select("id") + + type clients struct { + Id ID + } + + convert := func(fields []clients) []CurrentModuleAsSDKClient { + out := []CurrentModuleAsSDKClient{} + + for i := range fields { + val := CurrentModuleAsSDKClient{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "CurrentModuleAsSDKClient") + out = append(out, val) + } + + return out + } + var response []clients + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// A unique identifier for this CurrentModuleAsSDK. +func (r *CurrentModuleAsSDK) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *CurrentModuleAsSDK) XXX_GraphQLType() string { + return "CurrentModuleAsSDK" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *CurrentModuleAsSDK) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *CurrentModuleAsSDK) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *CurrentModuleAsSDK) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *CurrentModuleAsSDK) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = CurrentModuleAsSDK{query: selectNode(dag.query, id, "CurrentModuleAsSDK")} + return nil +} + +// The managed modules relevant to the bound workspace cwd: every module at or below it, plus the nearest enclosing module when the cwd itself is not managed. +func (r *CurrentModuleAsSDK) Modules(ctx context.Context) ([]CurrentModuleAsSDKModule, error) { + q := r.query.Select("modules") + + q = q.Select("id") + + type modules struct { + Id ID + } + + convert := func(fields []modules) []CurrentModuleAsSDKModule { + out := []CurrentModuleAsSDKModule{} + + for i := range fields { + val := CurrentModuleAsSDKModule{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "CurrentModuleAsSDKModule") + out = append(out, val) + } + + return out + } + var response []modules + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// The user-facing name of this SDK in the workspace. +func (r *CurrentModuleAsSDK) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this CurrentModuleAsSDK as a Node. +// This is a local type conversion — no GraphQL call. +func (r *CurrentModuleAsSDK) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A generated client the current SDK produces in the workspace. +type CurrentModuleAsSDKClient struct { + query *querybuilder.Selection + + id *ID + module *string + path *string + pin *string +} + +func (r *CurrentModuleAsSDKClient) WithGraphQLQuery(q *querybuilder.Selection) *CurrentModuleAsSDKClient { + return &CurrentModuleAsSDKClient{ + query: q, + } +} + +// A unique identifier for this CurrentModuleAsSDKClient. +func (r *CurrentModuleAsSDKClient) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *CurrentModuleAsSDKClient) XXX_GraphQLType() string { + return "CurrentModuleAsSDKClient" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *CurrentModuleAsSDKClient) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *CurrentModuleAsSDKClient) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *CurrentModuleAsSDKClient) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *CurrentModuleAsSDKClient) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = CurrentModuleAsSDKClient{query: selectNode(dag.query, id, "CurrentModuleAsSDKClient")} + return nil +} + +// The module the client is bound to (workspace-relative path or canonical ref). +func (r *CurrentModuleAsSDKClient) Module(ctx context.Context) (string, error) { + if r.module != nil { + return *r.module, nil + } + q := r.query.Select("module") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The resolved module source this client is bound to, including its dependency closure and pinned version. +func (r *CurrentModuleAsSDKClient) ModuleSource() *ModuleSource { + q := r.query.Select("moduleSource") + + return &ModuleSource{ + query: q, + } +} + +// Workspace-root-relative path of the generated client. +func (r *CurrentModuleAsSDKClient) Path(ctx context.Context) (string, error) { + if r.path != nil { + return *r.path, nil + } + q := r.query.Select("path") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The pinned version of the bound module, if any. +func (r *CurrentModuleAsSDKClient) Pin(ctx context.Context) (string, error) { + if r.pin != nil { + return *r.pin, nil + } + q := r.query.Select("pin") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this CurrentModuleAsSDKClient as a Node. +// This is a local type conversion — no GraphQL call. +func (r *CurrentModuleAsSDKClient) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A workspace-local module managed by the current SDK. +type CurrentModuleAsSDKModule struct { + query *querybuilder.Selection + + id *ID + path *string +} + +func (r *CurrentModuleAsSDKModule) WithGraphQLQuery(q *querybuilder.Selection) *CurrentModuleAsSDKModule { + return &CurrentModuleAsSDKModule{ + query: q, + } +} + +// A unique identifier for this CurrentModuleAsSDKModule. +func (r *CurrentModuleAsSDKModule) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *CurrentModuleAsSDKModule) XXX_GraphQLType() string { + return "CurrentModuleAsSDKModule" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *CurrentModuleAsSDKModule) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *CurrentModuleAsSDKModule) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *CurrentModuleAsSDKModule) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *CurrentModuleAsSDKModule) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = CurrentModuleAsSDKModule{query: selectNode(dag.query, id, "CurrentModuleAsSDKModule")} + return nil +} + +// Workspace-root-relative path to the managed module. +func (r *CurrentModuleAsSDKModule) Path(ctx context.Context) (string, error) { + if r.path != nil { + return *r.path, nil + } + q := r.query.Select("path") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this CurrentModuleAsSDKModule as a Node. +// This is a local type conversion — no GraphQL call. +func (r *CurrentModuleAsSDKModule) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +type DiffStat struct { + query *querybuilder.Selection + + addedLines *int + id *ID + kind *DiffStatKind + oldPath *string + path *string + removedLines *int +} + +func (r *DiffStat) WithGraphQLQuery(q *querybuilder.Selection) *DiffStat { + return &DiffStat{ + query: q, + } +} + +// Number of added lines for this path. +func (r *DiffStat) AddedLines(ctx context.Context) (int, error) { + if r.addedLines != nil { + return *r.addedLines, nil + } + q := r.query.Select("addedLines") + + var response int + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this DiffStat. +func (r *DiffStat) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *DiffStat) XXX_GraphQLType() string { + return "DiffStat" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *DiffStat) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *DiffStat) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *DiffStat) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *DiffStat) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = DiffStat{query: selectNode(dag.query, id, "DiffStat")} + return nil +} + +// Type of change. +func (r *DiffStat) Kind(ctx context.Context) (DiffStatKind, error) { + if r.kind != nil { + return *r.kind, nil + } + q := r.query.Select("kind") + + var response DiffStatKind + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Previous path of the file, set only for renames. +func (r *DiffStat) OldPath(ctx context.Context) (string, error) { + if r.oldPath != nil { + return *r.oldPath, nil + } + q := r.query.Select("oldPath") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Path of the changed file or directory. +func (r *DiffStat) Path(ctx context.Context) (string, error) { + if r.path != nil { + return *r.path, nil + } + q := r.query.Select("path") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Number of removed lines for this path. +func (r *DiffStat) RemovedLines(ctx context.Context) (int, error) { + if r.removedLines != nil { + return *r.removedLines, nil + } + q := r.query.Select("removedLines") + + var response int + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this DiffStat as a Node. +// This is a local type conversion — no GraphQL call. +func (r *DiffStat) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A directory. +type Directory struct { + query *querybuilder.Selection + + digest *string + exists *bool + export *string + findUp *string + id *ID + name *string + sync *ID +} +type WithDirectoryFunc func(r *Directory) *Directory + +// With calls the provided function with current Directory. +// +// This is useful for reusability and readability by not breaking the calling chain. +func (r *Directory) With(f WithDirectoryFunc) *Directory { + return f(r) +} + +func (r *Directory) WithGraphQLQuery(q *querybuilder.Selection) *Directory { + return &Directory{ + query: q, + } +} + +// Converts this directory to a local git repository +func (r *Directory) AsGit() *GitRepository { + q := r.query.Select("asGit") + + return &GitRepository{ + query: q, + } +} + +// DirectoryAsModuleOpts contains options for Directory.AsModule +type DirectoryAsModuleOpts struct { + // 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. + // + // Default: "." + SourceRootPath string +} + +// Load the directory as a Dagger module source +func (r *Directory) AsModule(opts ...DirectoryAsModuleOpts) *Module { + q := r.query.Select("asModule") + for i := len(opts) - 1; i >= 0; i-- { + // `sourceRootPath` optional argument + if !querybuilder.IsZeroValue(opts[i].SourceRootPath) { + q = q.Arg("sourceRootPath", opts[i].SourceRootPath) + } + } + + return &Module{ + query: q, + } +} + +// DirectoryAsModuleSourceOpts contains options for Directory.AsModuleSource +type DirectoryAsModuleSourceOpts struct { + // 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. + // + // Default: "." + SourceRootPath string +} + +// Load the directory as a Dagger module source +func (r *Directory) AsModuleSource(opts ...DirectoryAsModuleSourceOpts) *ModuleSource { + q := r.query.Select("asModuleSource") + for i := len(opts) - 1; i >= 0; i-- { + // `sourceRootPath` optional argument + if !querybuilder.IsZeroValue(opts[i].SourceRootPath) { + q = q.Arg("sourceRootPath", opts[i].SourceRootPath) + } + } + + return &ModuleSource{ + query: q, + } +} + +// DirectoryAsWorkspaceOpts contains options for Directory.AsWorkspace +type DirectoryAsWorkspaceOpts struct { + // Current working directory inside the workspace root. Defaults to the workspace root. + // + // Default: "/" + Cwd string +} + +// Creates a synthetic workspace from this directory. +func (r *Directory) AsWorkspace(opts ...DirectoryAsWorkspaceOpts) *Workspace { + q := r.query.Select("asWorkspace") + for i := len(opts) - 1; i >= 0; i-- { + // `cwd` optional argument + if !querybuilder.IsZeroValue(opts[i].Cwd) { + q = q.Arg("cwd", opts[i].Cwd) + } + } + + return &Workspace{ + query: q, + } +} + +// Return the difference between this directory and another directory, typically an older snapshot. +// +// The difference is encoded as a changeset, which also tracks removed files, and can be applied to other directories. +func (r *Directory) Changes(from *Directory) *Changeset { + assertNotNil("from", from) + q := r.query.Select("changes") + q = q.Arg("from", from) + + return &Changeset{ + query: q, + } +} + +// Change the owner of the directory contents recursively. +func (r *Directory) Chown(path string, owner string) *Directory { + q := r.query.Select("chown") + q = q.Arg("path", path) + q = q.Arg("owner", owner) + + return &Directory{ + query: q, + } +} + +// Return the difference between this directory and an another directory. The difference is encoded as a directory. +func (r *Directory) Diff(other *Directory) *Directory { + assertNotNil("other", other) + q := r.query.Select("diff") + q = q.Arg("other", other) + + return &Directory{ + query: q, + } +} + +// 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. +func (r *Directory) Digest(ctx context.Context) (string, error) { + if r.digest != nil { + return *r.digest, nil + } + q := r.query.Select("digest") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Retrieves a directory at the given path. +func (r *Directory) Directory(path string) *Directory { + q := r.query.Select("directory") + q = q.Arg("path", path) + + return &Directory{ + query: q, + } +} + +// DirectoryDockerBuildOpts contains options for Directory.DockerBuild +type DirectoryDockerBuildOpts struct { + // Path to the Dockerfile to use (e.g., "frontend.Dockerfile"). + // + // Default: "Dockerfile" + Dockerfile string + // The platform to build. + Platform Platform + // Build arguments to use in the build. + BuildArgs []BuildArg + // Target build stage to build. + Target string + // Secrets to pass to the build. + // + // They will be mounted at /run/secrets/[secret-name]. + Secrets []*Secret + // 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. + NoInit bool + // A socket to use for SSH authentication during the build + // + // (e.g., for Dockerfile RUN --mount=type=ssh instructions). + // + // Typically obtained via host.unixSocket() pointing to the SSH_AUTH_SOCK. + SSH *Socket +} + +// 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. +func (r *Directory) DockerBuild(opts ...DirectoryDockerBuildOpts) *Container { + q := r.query.Select("dockerBuild") + for i := len(opts) - 1; i >= 0; i-- { + // `dockerfile` optional argument + if !querybuilder.IsZeroValue(opts[i].Dockerfile) { + q = q.Arg("dockerfile", opts[i].Dockerfile) + } + // `platform` optional argument + if !querybuilder.IsZeroValue(opts[i].Platform) { + q = q.Arg("platform", opts[i].Platform) + } + // `buildArgs` optional argument + if !querybuilder.IsZeroValue(opts[i].BuildArgs) { + q = q.Arg("buildArgs", opts[i].BuildArgs) + } + // `target` optional argument + if !querybuilder.IsZeroValue(opts[i].Target) { + q = q.Arg("target", opts[i].Target) + } + // `secrets` optional argument + if !querybuilder.IsZeroValue(opts[i].Secrets) { + q = q.Arg("secrets", opts[i].Secrets) + } + // `noInit` optional argument + if !querybuilder.IsZeroValue(opts[i].NoInit) { + q = q.Arg("noInit", opts[i].NoInit) + } + // `ssh` optional argument + if !querybuilder.IsZeroValue(opts[i].SSH) { + q = q.Arg("ssh", opts[i].SSH) + } + } + + return &Container{ + query: q, + } +} + +// DirectoryEntriesOpts contains options for Directory.Entries +type DirectoryEntriesOpts struct { + // Location of the directory to look at (e.g., "/src"). + Path string +} + +// Returns a list of files and directories at the given path. +func (r *Directory) Entries(ctx context.Context, opts ...DirectoryEntriesOpts) ([]string, error) { + q := r.query.Select("entries") + for i := len(opts) - 1; i >= 0; i-- { + // `path` optional argument + if !querybuilder.IsZeroValue(opts[i].Path) { + q = q.Arg("path", opts[i].Path) + } + } + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// DirectoryExistsOpts contains options for Directory.Exists +type DirectoryExistsOpts struct { + // If specified, also validate the type of file (e.g. "REGULAR_TYPE", "DIRECTORY_TYPE", or "SYMLINK_TYPE"). + ExpectedType ExistsType + // If specified, do not follow symlinks. + DoNotFollowSymlinks bool +} + +// check if a file or directory exists +func (r *Directory) Exists(ctx context.Context, path string, opts ...DirectoryExistsOpts) (bool, error) { + if r.exists != nil { + return *r.exists, nil + } + q := r.query.Select("exists") + for i := len(opts) - 1; i >= 0; i-- { + // `expectedType` optional argument + if !querybuilder.IsZeroValue(opts[i].ExpectedType) { + q = q.Arg("expectedType", opts[i].ExpectedType) + } + // `doNotFollowSymlinks` optional argument + if !querybuilder.IsZeroValue(opts[i].DoNotFollowSymlinks) { + q = q.Arg("doNotFollowSymlinks", opts[i].DoNotFollowSymlinks) + } + } + q = q.Arg("path", path) + + var response bool + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// DirectoryExportOpts contains options for Directory.Export +type DirectoryExportOpts struct { + // 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. + Wipe bool +} + +// Writes the contents of the directory to a path on the host. +func (r *Directory) Export(ctx context.Context, path string, opts ...DirectoryExportOpts) (string, error) { + if r.export != nil { + return *r.export, nil + } + q := r.query.Select("export") + for i := len(opts) - 1; i >= 0; i-- { + // `wipe` optional argument + if !querybuilder.IsZeroValue(opts[i].Wipe) { + q = q.Arg("wipe", opts[i].Wipe) + } + } + q = q.Arg("path", path) + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Retrieve a file at the given path. +func (r *Directory) File(path string) *File { + q := r.query.Select("file") + q = q.Arg("path", path) + + return &File{ + query: q, + } +} + +// DirectoryFilterOpts contains options for Directory.Filter +type DirectoryFilterOpts struct { + // If set, paths matching one of these glob patterns is excluded from the new snapshot. Example: ["node_modules/", ".git*", ".env"] + Exclude []string + // If set, only paths matching one of these glob patterns is included in the new snapshot. Example: (e.g., ["app/", "package.*"]). + Include []string + // If set, apply .gitignore rules when filtering the directory. + Gitignore bool +} + +// Return a snapshot with some paths included or excluded +func (r *Directory) Filter(opts ...DirectoryFilterOpts) *Directory { + q := r.query.Select("filter") + for i := len(opts) - 1; i >= 0; i-- { + // `exclude` optional argument + if !querybuilder.IsZeroValue(opts[i].Exclude) { + q = q.Arg("exclude", opts[i].Exclude) + } + // `include` optional argument + if !querybuilder.IsZeroValue(opts[i].Include) { + q = q.Arg("include", opts[i].Include) + } + // `gitignore` optional argument + if !querybuilder.IsZeroValue(opts[i].Gitignore) { + q = q.Arg("gitignore", opts[i].Gitignore) + } + } + + return &Directory{ + query: q, + } +} + +// Search up the directory tree for a file or directory, and return its path. If no match, return null +func (r *Directory) FindUp(ctx context.Context, name string, start string) (string, error) { + if r.findUp != nil { + return *r.findUp, nil + } + q := r.query.Select("findUp") + q = q.Arg("name", name) + q = q.Arg("start", start) + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Returns a list of files and directories that matche the given pattern. +func (r *Directory) Glob(ctx context.Context, pattern string) ([]string, error) { + q := r.query.Select("glob") + q = q.Arg("pattern", pattern) + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this Directory. +func (r *Directory) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Directory) XXX_GraphQLType() string { + return "Directory" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Directory) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Directory) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Directory) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Directory) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = Directory{query: selectNode(dag.query, id, "Directory")} + return nil +} + +// Returns the name of the directory. +func (r *Directory) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// DirectorySearchOpts contains options for Directory.Search +type DirectorySearchOpts struct { + // Directory or file paths to search + Paths []string + // Glob patterns to match (e.g., "*.md") + Globs []string + // Interpret the pattern as a literal string instead of a regular expression. + Literal bool + // Enable searching across multiple lines. + Multiline bool + // Allow the . pattern to match newlines in multiline mode. + Dotall bool + // Enable case-insensitive matching. + Insensitive bool + // Honor .gitignore, .ignore, and .rgignore files. + SkipIgnored bool + // Skip hidden files (files starting with .). + SkipHidden bool + // Only return matching files, not lines and content + FilesOnly bool + // Limit the number of results to return + Limit int +} + +// Searches for content matching the given regular expression or literal string. +// +// Uses Rust regex syntax; escape literal ., [, ], {, }, | with backslashes. +func (r *Directory) Search(ctx context.Context, pattern string, opts ...DirectorySearchOpts) ([]SearchResult, error) { + q := r.query.Select("search") + for i := len(opts) - 1; i >= 0; i-- { + // `paths` optional argument + if !querybuilder.IsZeroValue(opts[i].Paths) { + q = q.Arg("paths", opts[i].Paths) + } + // `globs` optional argument + if !querybuilder.IsZeroValue(opts[i].Globs) { + q = q.Arg("globs", opts[i].Globs) + } + // `literal` optional argument + if !querybuilder.IsZeroValue(opts[i].Literal) { + q = q.Arg("literal", opts[i].Literal) + } + // `multiline` optional argument + if !querybuilder.IsZeroValue(opts[i].Multiline) { + q = q.Arg("multiline", opts[i].Multiline) + } + // `dotall` optional argument + if !querybuilder.IsZeroValue(opts[i].Dotall) { + q = q.Arg("dotall", opts[i].Dotall) + } + // `insensitive` optional argument + if !querybuilder.IsZeroValue(opts[i].Insensitive) { + q = q.Arg("insensitive", opts[i].Insensitive) + } + // `skipIgnored` optional argument + if !querybuilder.IsZeroValue(opts[i].SkipIgnored) { + q = q.Arg("skipIgnored", opts[i].SkipIgnored) + } + // `skipHidden` optional argument + if !querybuilder.IsZeroValue(opts[i].SkipHidden) { + q = q.Arg("skipHidden", opts[i].SkipHidden) + } + // `filesOnly` optional argument + if !querybuilder.IsZeroValue(opts[i].FilesOnly) { + q = q.Arg("filesOnly", opts[i].FilesOnly) + } + // `limit` optional argument + if !querybuilder.IsZeroValue(opts[i].Limit) { + q = q.Arg("limit", opts[i].Limit) + } + } + q = q.Arg("pattern", pattern) + + q = q.Select("id") + + type search struct { + Id ID + } + + convert := func(fields []search) []SearchResult { + out := []SearchResult{} + + for i := range fields { + val := SearchResult{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "SearchResult") + out = append(out, val) + } + + return out + } + var response []search + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// DirectoryStatOpts contains options for Directory.Stat +type DirectoryStatOpts struct { + // If specified, do not follow symlinks. + DoNotFollowSymlinks bool +} + +// Return file status +func (r *Directory) Stat(ctx context.Context, path string, opts ...DirectoryStatOpts) (*Stat, error) { + q := r.query.Select("stat") + for i := len(opts) - 1; i >= 0; i-- { + // `doNotFollowSymlinks` optional argument + if !querybuilder.IsZeroValue(opts[i].DoNotFollowSymlinks) { + q = q.Arg("doNotFollowSymlinks", opts[i].DoNotFollowSymlinks) + } + } + q = q.Arg("path", path) + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &Stat{ + query: selectNode(q.Root(), *objectID, "Stat"), + }, nil +} + +// Force evaluation in the engine. +func (r *Directory) Sync(ctx context.Context) (*Directory, error) { + q := r.query.Select("sync") + + var id ID + if err := q.Bind(&id).Execute(ctx); err != nil { + return nil, err + } + return &Directory{ + query: selectNode(q.Root(), id, "Directory"), + }, nil +} + +// DirectoryTerminalOpts contains options for Directory.Terminal +type DirectoryTerminalOpts struct { + // If set, override the default container used for the terminal. + Container *Container + // If set, override the container's default terminal command and invoke these command arguments instead. + Cmd []string + // Provides Dagger access to the executed command. + ExperimentalPrivilegedNesting bool + // 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. + InsecureRootCapabilities bool +} + +// Opens an interactive terminal in new container with this directory mounted inside. +func (r *Directory) Terminal(opts ...DirectoryTerminalOpts) *Directory { + q := r.query.Select("terminal") + for i := len(opts) - 1; i >= 0; i-- { + // `container` optional argument + if !querybuilder.IsZeroValue(opts[i].Container) { + q = q.Arg("container", opts[i].Container) + } + // `cmd` optional argument + if !querybuilder.IsZeroValue(opts[i].Cmd) { + q = q.Arg("cmd", opts[i].Cmd) + } + // `experimentalPrivilegedNesting` optional argument + if !querybuilder.IsZeroValue(opts[i].ExperimentalPrivilegedNesting) { + q = q.Arg("experimentalPrivilegedNesting", opts[i].ExperimentalPrivilegedNesting) + } + // `insecureRootCapabilities` optional argument + if !querybuilder.IsZeroValue(opts[i].InsecureRootCapabilities) { + q = q.Arg("insecureRootCapabilities", opts[i].InsecureRootCapabilities) + } + } + + return &Directory{ + query: q, + } +} + +// Return a directory with changes from another directory applied to it. +func (r *Directory) WithChanges(changes *Changeset) *Directory { + assertNotNil("changes", changes) + q := r.query.Select("withChanges") + q = q.Arg("changes", changes) + + return &Directory{ + query: q, + } +} + +// DirectoryWithDirectoryOpts contains options for Directory.WithDirectory +type DirectoryWithDirectoryOpts struct { + // Exclude artifacts that match the given pattern (e.g., ["node_modules/", ".git*"]). + Exclude []string + // Include only artifacts that match the given pattern (e.g., ["app/", "package.*"]). + Include []string + // Apply .gitignore filter rules inside the directory + Gitignore bool + // A user:group to set for the copied 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. + Owner string + // Permission given to the copied directory and contents (e.g., 0755). + Permissions int +} + +// Return a snapshot with a directory added +func (r *Directory) WithDirectory(path string, source *Directory, opts ...DirectoryWithDirectoryOpts) *Directory { + assertNotNil("source", source) + q := r.query.Select("withDirectory") + for i := len(opts) - 1; i >= 0; i-- { + // `exclude` optional argument + if !querybuilder.IsZeroValue(opts[i].Exclude) { + q = q.Arg("exclude", opts[i].Exclude) + } + // `include` optional argument + if !querybuilder.IsZeroValue(opts[i].Include) { + q = q.Arg("include", opts[i].Include) + } + // `gitignore` optional argument + if !querybuilder.IsZeroValue(opts[i].Gitignore) { + q = q.Arg("gitignore", opts[i].Gitignore) + } + // `owner` optional argument + if !querybuilder.IsZeroValue(opts[i].Owner) { + q = q.Arg("owner", opts[i].Owner) + } + // `permissions` optional argument + if !querybuilder.IsZeroValue(opts[i].Permissions) { + q = q.Arg("permissions", opts[i].Permissions) + } + } + q = q.Arg("path", path) + q = q.Arg("source", source) + + return &Directory{ + query: q, + } +} + +// Raise an error. +func (r *Directory) WithError(err string) *Directory { + q := r.query.Select("withError") + q = q.Arg("err", err) + + return &Directory{ + query: q, + } +} + +// DirectoryWithFileOpts contains options for Directory.WithFile +type DirectoryWithFileOpts struct { + // Permission given to the copied file (e.g., 0600). + Permissions int + // A user:group to set for the copied 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. + Owner string +} + +// Retrieves this directory plus the contents of the given file copied to the given path. +func (r *Directory) WithFile(path string, source *File, opts ...DirectoryWithFileOpts) *Directory { + assertNotNil("source", source) + q := r.query.Select("withFile") + for i := len(opts) - 1; i >= 0; i-- { + // `permissions` optional argument + if !querybuilder.IsZeroValue(opts[i].Permissions) { + q = q.Arg("permissions", opts[i].Permissions) + } + // `owner` optional argument + if !querybuilder.IsZeroValue(opts[i].Owner) { + q = q.Arg("owner", opts[i].Owner) + } + } + q = q.Arg("path", path) + q = q.Arg("source", source) + + return &Directory{ + query: q, + } +} + +// DirectoryWithFilesOpts contains options for Directory.WithFiles +type DirectoryWithFilesOpts struct { + // Permission given to the copied files (e.g., 0600). + Permissions int +} + +// Retrieves this directory plus the contents of the given files copied to the given path. +func (r *Directory) WithFiles(path string, sources []*File, opts ...DirectoryWithFilesOpts) *Directory { + q := r.query.Select("withFiles") + for i := len(opts) - 1; i >= 0; i-- { + // `permissions` optional argument + if !querybuilder.IsZeroValue(opts[i].Permissions) { + q = q.Arg("permissions", opts[i].Permissions) + } + } + q = q.Arg("path", path) + q = q.Arg("sources", sources) + + return &Directory{ + query: q, + } +} + +// DirectoryWithNewDirectoryOpts contains options for Directory.WithNewDirectory +type DirectoryWithNewDirectoryOpts struct { + // Permission granted to the created directory (e.g., 0777). + // + // Default: 420 + Permissions int +} + +// Retrieves this directory plus a new directory created at the given path. +func (r *Directory) WithNewDirectory(path string, opts ...DirectoryWithNewDirectoryOpts) *Directory { + q := r.query.Select("withNewDirectory") + for i := len(opts) - 1; i >= 0; i-- { + // `permissions` optional argument + if !querybuilder.IsZeroValue(opts[i].Permissions) { + q = q.Arg("permissions", opts[i].Permissions) + } + } + q = q.Arg("path", path) + + return &Directory{ + query: q, + } +} + +// DirectoryWithNewFileOpts contains options for Directory.WithNewFile +type DirectoryWithNewFileOpts struct { + // Permissions of the new file. Example: 0600 + // + // Default: 420 + Permissions int +} + +// Return a snapshot with a new file added +func (r *Directory) WithNewFile(path string, contents string, opts ...DirectoryWithNewFileOpts) *Directory { + q := r.query.Select("withNewFile") + for i := len(opts) - 1; i >= 0; i-- { + // `permissions` optional argument + if !querybuilder.IsZeroValue(opts[i].Permissions) { + q = q.Arg("permissions", opts[i].Permissions) + } + } + q = q.Arg("path", path) + q = q.Arg("contents", contents) + + return &Directory{ + query: q, + } +} + +// DirectoryWithPatchOpts contains options for Directory.WithPatch +type DirectoryWithPatchOpts struct { + // How to handle hunks that no longer apply to the target content: fail (default), or apply what fits and leave git-style conflict markers where it doesn't. + // + // Default: FAIL + OnConflict PatchConflict +} + +// Retrieves this directory with the given Git-compatible patch applied. +// +// Experimental: This API is highly experimental and may be removed or replaced entirely. +func (r *Directory) WithPatch(patch string, opts ...DirectoryWithPatchOpts) *Directory { + q := r.query.Select("withPatch") + for i := len(opts) - 1; i >= 0; i-- { + // `onConflict` optional argument + if !querybuilder.IsZeroValue(opts[i].OnConflict) { + q = q.Arg("onConflict", opts[i].OnConflict) + } + } + q = q.Arg("patch", patch) + + return &Directory{ + query: q, + } +} + +// DirectoryWithPatchFileOpts contains options for Directory.WithPatchFile +type DirectoryWithPatchFileOpts struct { + // How to handle hunks that no longer apply to the target content: fail (default), or apply what fits and leave git-style conflict markers where it doesn't. + // + // Default: FAIL + OnConflict PatchConflict +} + +// Retrieves this directory with the given Git-compatible patch file applied. +// +// Experimental: This API is highly experimental and may be removed or replaced entirely. +func (r *Directory) WithPatchFile(patch *File, opts ...DirectoryWithPatchFileOpts) *Directory { + assertNotNil("patch", patch) + q := r.query.Select("withPatchFile") + for i := len(opts) - 1; i >= 0; i-- { + // `onConflict` optional argument + if !querybuilder.IsZeroValue(opts[i].OnConflict) { + q = q.Arg("onConflict", opts[i].OnConflict) + } + } + q = q.Arg("patch", patch) + + return &Directory{ + query: q, + } +} + +// Return a snapshot with a symlink +func (r *Directory) WithSymlink(target string, linkName string) *Directory { + q := r.query.Select("withSymlink") + q = q.Arg("target", target) + q = q.Arg("linkName", linkName) + + return &Directory{ + query: q, + } +} + +// Retrieves this directory with all file/dir timestamps set to the given time. +func (r *Directory) WithTimestamps(timestamp int) *Directory { + q := r.query.Select("withTimestamps") + q = q.Arg("timestamp", timestamp) + + return &Directory{ + query: q, + } +} + +// Return a snapshot with a subdirectory removed +func (r *Directory) WithoutDirectory(path string) *Directory { + q := r.query.Select("withoutDirectory") + q = q.Arg("path", path) + + return &Directory{ + query: q, + } +} + +// Return a snapshot with a file removed +func (r *Directory) WithoutFile(path string) *Directory { + q := r.query.Select("withoutFile") + q = q.Arg("path", path) + + return &Directory{ + query: q, + } +} + +// Return a snapshot with files removed +func (r *Directory) WithoutFiles(paths []string) *Directory { + q := r.query.Select("withoutFiles") + q = q.Arg("paths", paths) + + return &Directory{ + query: q, + } +} + +// AsExportable returns this Directory as a Exportable. +// This is a local type conversion — no GraphQL call. +func (r *Directory) AsExportable() Exportable { + return &ExportableClient{ + query: r.query, + } +} + +// AsNode returns this Directory as a Node. +// This is a local type conversion — no GraphQL call. +func (r *Directory) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// AsSyncer returns this Directory as a Syncer. +// This is a local type conversion — no GraphQL call. +func (r *Directory) AsSyncer() Syncer { + return &SyncerClient{ + query: r.query, + } +} + +// A definition of a custom enum defined in a Module. +type EnumTypeDef struct { + query *querybuilder.Selection + + description *string + id *ID + name *string + sourceModuleName *string +} + +func (r *EnumTypeDef) WithGraphQLQuery(q *querybuilder.Selection) *EnumTypeDef { + return &EnumTypeDef{ + query: q, + } +} + +// A doc string for the enum, if any. +func (r *EnumTypeDef) Description(ctx context.Context) (string, error) { + if r.description != nil { + return *r.description, nil + } + q := r.query.Select("description") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this EnumTypeDef. +func (r *EnumTypeDef) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *EnumTypeDef) XXX_GraphQLType() string { + return "EnumTypeDef" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *EnumTypeDef) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *EnumTypeDef) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *EnumTypeDef) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *EnumTypeDef) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = EnumTypeDef{query: selectNode(dag.query, id, "EnumTypeDef")} + return nil +} + +// The members of the enum. +func (r *EnumTypeDef) Members(ctx context.Context) ([]EnumValueTypeDef, error) { + q := r.query.Select("members") + + q = q.Select("id") + + type members struct { + Id ID + } + + convert := func(fields []members) []EnumValueTypeDef { + out := []EnumValueTypeDef{} + + for i := range fields { + val := EnumValueTypeDef{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "EnumValueTypeDef") + out = append(out, val) + } + + return out + } + var response []members + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// The name of the enum. +func (r *EnumTypeDef) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The location of this enum declaration. +func (r *EnumTypeDef) SourceMap(ctx context.Context) (*SourceMap, error) { + q := r.query.Select("sourceMap") + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &SourceMap{ + query: selectNode(q.Root(), *objectID, "SourceMap"), + }, nil +} + +// If this EnumTypeDef is associated with a Module, the name of the module. Unset otherwise. +func (r *EnumTypeDef) SourceModuleName(ctx context.Context) (string, error) { + if r.sourceModuleName != nil { + return *r.sourceModuleName, nil + } + q := r.query.Select("sourceModuleName") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The members of the enum. +// +// Deprecated: use members instead +func (r *EnumTypeDef) Values(ctx context.Context) ([]EnumValueTypeDef, error) { + q := r.query.Select("values") + + q = q.Select("id") + + type values struct { + Id ID + } + + convert := func(fields []values) []EnumValueTypeDef { + out := []EnumValueTypeDef{} + + for i := range fields { + val := EnumValueTypeDef{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "EnumValueTypeDef") + out = append(out, val) + } + + return out + } + var response []values + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// AsNode returns this EnumTypeDef as a Node. +// This is a local type conversion — no GraphQL call. +func (r *EnumTypeDef) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A definition of a value in a custom enum defined in a Module. +type EnumValueTypeDef struct { + query *querybuilder.Selection + + deprecated *string + description *string + id *ID + name *string + value *string +} + +func (r *EnumValueTypeDef) WithGraphQLQuery(q *querybuilder.Selection) *EnumValueTypeDef { + return &EnumValueTypeDef{ + query: q, + } +} + +// The reason this enum member is deprecated, if any. +func (r *EnumValueTypeDef) Deprecated(ctx context.Context) (string, error) { + if r.deprecated != nil { + return *r.deprecated, nil + } + q := r.query.Select("deprecated") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A doc string for the enum member, if any. +func (r *EnumValueTypeDef) Description(ctx context.Context) (string, error) { + if r.description != nil { + return *r.description, nil + } + q := r.query.Select("description") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this EnumValueTypeDef. +func (r *EnumValueTypeDef) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *EnumValueTypeDef) XXX_GraphQLType() string { + return "EnumValueTypeDef" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *EnumValueTypeDef) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *EnumValueTypeDef) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *EnumValueTypeDef) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *EnumValueTypeDef) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = EnumValueTypeDef{query: selectNode(dag.query, id, "EnumValueTypeDef")} + return nil +} + +// The name of the enum member. +func (r *EnumValueTypeDef) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The location of this enum member declaration. +func (r *EnumValueTypeDef) SourceMap(ctx context.Context) (*SourceMap, error) { + q := r.query.Select("sourceMap") + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &SourceMap{ + query: selectNode(q.Root(), *objectID, "SourceMap"), + }, nil +} + +// The value of the enum member +func (r *EnumValueTypeDef) Value(ctx context.Context) (string, error) { + if r.value != nil { + return *r.value, nil + } + q := r.query.Select("value") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this EnumValueTypeDef as a Node. +// This is a local type conversion — no GraphQL call. +func (r *EnumValueTypeDef) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A collection of environment variables. +type EnvFile struct { + query *querybuilder.Selection + + exists *bool + get *string + id *ID +} +type WithEnvFileFunc func(r *EnvFile) *EnvFile + +// With calls the provided function with current EnvFile. +// +// This is useful for reusability and readability by not breaking the calling chain. +func (r *EnvFile) With(f WithEnvFileFunc) *EnvFile { + return f(r) +} + +func (r *EnvFile) WithGraphQLQuery(q *querybuilder.Selection) *EnvFile { + return &EnvFile{ + query: q, + } +} + +// Return as a file +func (r *EnvFile) AsFile() *File { + q := r.query.Select("asFile") + + return &File{ + query: q, + } +} + +// Check if a variable exists +func (r *EnvFile) Exists(ctx context.Context, name string) (bool, error) { + if r.exists != nil { + return *r.exists, nil + } + q := r.query.Select("exists") + q = q.Arg("name", name) + + var response bool + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// EnvFileGetOpts contains options for EnvFile.Get +type EnvFileGetOpts struct { + // Return the value exactly as written to the file. No quote removal or variable expansion + Raw bool +} + +// Lookup a variable (last occurrence wins) and return its value, or an empty string +func (r *EnvFile) Get(ctx context.Context, name string, opts ...EnvFileGetOpts) (string, error) { + if r.get != nil { + return *r.get, nil + } + q := r.query.Select("get") + for i := len(opts) - 1; i >= 0; i-- { + // `raw` optional argument + if !querybuilder.IsZeroValue(opts[i].Raw) { + q = q.Arg("raw", opts[i].Raw) + } + } + q = q.Arg("name", name) + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this EnvFile. +func (r *EnvFile) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *EnvFile) XXX_GraphQLType() string { + return "EnvFile" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *EnvFile) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *EnvFile) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *EnvFile) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *EnvFile) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = EnvFile{query: selectNode(dag.query, id, "EnvFile")} + return nil +} + +// Filters variables by prefix and removes the pref from keys. Variables without the prefix are excluded. For example, with the prefix "MY_APP_" and variables: MY_APP_TOKEN=topsecret MY_APP_NAME=hello FOO=bar the resulting environment will contain: TOKEN=topsecret NAME=hello +func (r *EnvFile) Namespace(prefix string) *EnvFile { + q := r.query.Select("namespace") + q = q.Arg("prefix", prefix) + + return &EnvFile{ + query: q, + } +} + +// EnvFileVariablesOpts contains options for EnvFile.Variables +type EnvFileVariablesOpts struct { + // Return values exactly as written to the file. No quote removal or variable expansion + Raw bool +} + +// Return all variables +func (r *EnvFile) Variables(ctx context.Context, opts ...EnvFileVariablesOpts) ([]EnvVariable, error) { + q := r.query.Select("variables") + for i := len(opts) - 1; i >= 0; i-- { + // `raw` optional argument + if !querybuilder.IsZeroValue(opts[i].Raw) { + q = q.Arg("raw", opts[i].Raw) + } + } + + q = q.Select("id") + + type variables struct { + Id ID + } + + convert := func(fields []variables) []EnvVariable { + out := []EnvVariable{} + + for i := range fields { + val := EnvVariable{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "EnvVariable") + out = append(out, val) + } + + return out + } + var response []variables + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// Add a variable +func (r *EnvFile) WithVariable(name string, value string) *EnvFile { + q := r.query.Select("withVariable") + q = q.Arg("name", name) + q = q.Arg("value", value) + + return &EnvFile{ + query: q, + } +} + +// Remove all occurrences of the named variable +func (r *EnvFile) WithoutVariable(name string) *EnvFile { + q := r.query.Select("withoutVariable") + q = q.Arg("name", name) + + return &EnvFile{ + query: q, + } +} + +// AsNode returns this EnvFile as a Node. +// This is a local type conversion — no GraphQL call. +func (r *EnvFile) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// An environment variable name and value. +type EnvVariable struct { + query *querybuilder.Selection + + id *ID + name *string + value *string +} + +func (r *EnvVariable) WithGraphQLQuery(q *querybuilder.Selection) *EnvVariable { + return &EnvVariable{ + query: q, + } +} + +// A unique identifier for this EnvVariable. +func (r *EnvVariable) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *EnvVariable) XXX_GraphQLType() string { + return "EnvVariable" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *EnvVariable) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *EnvVariable) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *EnvVariable) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *EnvVariable) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = EnvVariable{query: selectNode(dag.query, id, "EnvVariable")} + return nil +} + +// The environment variable name. +func (r *EnvVariable) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The environment variable value. +func (r *EnvVariable) Value(ctx context.Context) (string, error) { + if r.value != nil { + return *r.value, nil + } + q := r.query.Select("value") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this EnvVariable as a Node. +// This is a local type conversion — no GraphQL call. +func (r *EnvVariable) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +type Error struct { + query *querybuilder.Selection + + id *ID + message *string +} +type WithErrorFunc func(r *Error) *Error + +// With calls the provided function with current Error. +// +// This is useful for reusability and readability by not breaking the calling chain. +func (r *Error) With(f WithErrorFunc) *Error { + return f(r) +} + +func (r *Error) WithGraphQLQuery(q *querybuilder.Selection) *Error { + return &Error{ + query: q, + } +} + +// A unique identifier for this Error. +func (r *Error) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Error) XXX_GraphQLType() string { + return "Error" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Error) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Error) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Error) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Error) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = Error{query: selectNode(dag.query, id, "Error")} + return nil +} + +// A description of the error. +func (r *Error) Message(ctx context.Context) (string, error) { + if r.message != nil { + return *r.message, nil + } + q := r.query.Select("message") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The extensions of the error. +func (r *Error) Values(ctx context.Context) ([]ErrorValue, error) { + q := r.query.Select("values") + + q = q.Select("id") + + type values struct { + Id ID + } + + convert := func(fields []values) []ErrorValue { + out := []ErrorValue{} + + for i := range fields { + val := ErrorValue{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "ErrorValue") + out = append(out, val) + } + + return out + } + var response []values + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// Add a value to the error. +func (r *Error) WithValue(name string, value JSON) *Error { + q := r.query.Select("withValue") + q = q.Arg("name", name) + q = q.Arg("value", value) + + return &Error{ + query: q, + } +} + +// AsNode returns this Error as a Node. +// This is a local type conversion — no GraphQL call. +func (r *Error) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +type ErrorValue struct { + query *querybuilder.Selection + + id *ID + name *string + value *JSON +} + +func (r *ErrorValue) WithGraphQLQuery(q *querybuilder.Selection) *ErrorValue { + return &ErrorValue{ + query: q, + } +} + +// A unique identifier for this ErrorValue. +func (r *ErrorValue) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *ErrorValue) XXX_GraphQLType() string { + return "ErrorValue" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *ErrorValue) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *ErrorValue) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *ErrorValue) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *ErrorValue) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = ErrorValue{query: selectNode(dag.query, id, "ErrorValue")} + return nil +} + +// The name of the value. +func (r *ErrorValue) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The value. +func (r *ErrorValue) Value(ctx context.Context) (JSON, error) { + if r.value != nil { + return *r.value, nil + } + q := r.query.Select("value") + + var response JSON + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this ErrorValue as a Node. +// This is a local type conversion — no GraphQL call. +func (r *ErrorValue) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// 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). +type FieldTypeDef struct { + query *querybuilder.Selection + + deprecated *string + description *string + id *ID + name *string +} + +func (r *FieldTypeDef) WithGraphQLQuery(q *querybuilder.Selection) *FieldTypeDef { + return &FieldTypeDef{ + query: q, + } +} + +// The reason this enum member is deprecated, if any. +func (r *FieldTypeDef) Deprecated(ctx context.Context) (string, error) { + if r.deprecated != nil { + return *r.deprecated, nil + } + q := r.query.Select("deprecated") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A doc string for the field, if any. +func (r *FieldTypeDef) Description(ctx context.Context) (string, error) { + if r.description != nil { + return *r.description, nil + } + q := r.query.Select("description") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this FieldTypeDef. +func (r *FieldTypeDef) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *FieldTypeDef) XXX_GraphQLType() string { + return "FieldTypeDef" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *FieldTypeDef) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *FieldTypeDef) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *FieldTypeDef) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *FieldTypeDef) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = FieldTypeDef{query: selectNode(dag.query, id, "FieldTypeDef")} + return nil +} + +// The name of the field in lowerCamelCase format. +func (r *FieldTypeDef) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The location of this field declaration. +func (r *FieldTypeDef) SourceMap(ctx context.Context) (*SourceMap, error) { + q := r.query.Select("sourceMap") + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &SourceMap{ + query: selectNode(q.Root(), *objectID, "SourceMap"), + }, nil +} + +// The type of the field. +func (r *FieldTypeDef) TypeDef() *TypeDef { + q := r.query.Select("typeDef") + + return &TypeDef{ + query: q, + } +} + +// AsNode returns this FieldTypeDef as a Node. +// This is a local type conversion — no GraphQL call. +func (r *FieldTypeDef) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A file. +type File struct { + query *querybuilder.Selection + + contents *string + digest *string + export *string + id *ID + name *string + size *int + sync *ID +} +type WithFileFunc func(r *File) *File + +// With calls the provided function with current File. +// +// This is useful for reusability and readability by not breaking the calling chain. +func (r *File) With(f WithFileFunc) *File { + return f(r) +} + +func (r *File) WithGraphQLQuery(q *querybuilder.Selection) *File { + return &File{ + query: q, + } +} + +// FileAsEnvFileOpts contains options for File.AsEnvFile +type FileAsEnvFileOpts struct { + // Replace "${VAR}" or "$VAR" with the value of other vars + // Deprecated: Variable expansion is now enabled by default + Expand bool +} + +// Parse as an env file +func (r *File) AsEnvFile(opts ...FileAsEnvFileOpts) *EnvFile { + q := r.query.Select("asEnvFile") + for i := len(opts) - 1; i >= 0; i-- { + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + + return &EnvFile{ + query: q, + } +} + +// Parse the file contents as JSON. +func (r *File) AsJSON() *JSONValue { + q := r.query.Select("asJSON") + + return &JSONValue{ + query: q, + } +} + +// Change the owner of the file recursively. +func (r *File) Chown(owner string) *File { + q := r.query.Select("chown") + q = q.Arg("owner", owner) + + return &File{ + query: q, + } +} + +// FileContentsOpts contains options for File.Contents +type FileContentsOpts struct { + // Start reading after this line + OffsetLines int + // Maximum number of lines to read + LimitLines int +} + +// Retrieves the contents of the file. +func (r *File) Contents(ctx context.Context, opts ...FileContentsOpts) (string, error) { + if r.contents != nil { + return *r.contents, nil + } + q := r.query.Select("contents") + for i := len(opts) - 1; i >= 0; i-- { + // `offsetLines` optional argument + if !querybuilder.IsZeroValue(opts[i].OffsetLines) { + q = q.Arg("offsetLines", opts[i].OffsetLines) + } + // `limitLines` optional argument + if !querybuilder.IsZeroValue(opts[i].LimitLines) { + q = q.Arg("limitLines", opts[i].LimitLines) + } + } + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// FileDigestOpts contains options for File.Digest +type FileDigestOpts struct { + // If true, exclude metadata from the digest. + ExcludeMetadata bool +} + +// 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. +func (r *File) Digest(ctx context.Context, opts ...FileDigestOpts) (string, error) { + if r.digest != nil { + return *r.digest, nil + } + q := r.query.Select("digest") + for i := len(opts) - 1; i >= 0; i-- { + // `excludeMetadata` optional argument + if !querybuilder.IsZeroValue(opts[i].ExcludeMetadata) { + q = q.Arg("excludeMetadata", opts[i].ExcludeMetadata) + } + } + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// FileExportOpts contains options for File.Export +type FileExportOpts struct { + // If allowParentDirPath is true, the path argument can be a directory path, in which case the file will be created in that directory. + AllowParentDirPath bool +} + +// Writes the file to a file path on the host. +func (r *File) Export(ctx context.Context, path string, opts ...FileExportOpts) (string, error) { + if r.export != nil { + return *r.export, nil + } + q := r.query.Select("export") + for i := len(opts) - 1; i >= 0; i-- { + // `allowParentDirPath` optional argument + if !querybuilder.IsZeroValue(opts[i].AllowParentDirPath) { + q = q.Arg("allowParentDirPath", opts[i].AllowParentDirPath) + } + } + q = q.Arg("path", path) + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this File. +func (r *File) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *File) XXX_GraphQLType() string { + return "File" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *File) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *File) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *File) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *File) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = File{query: selectNode(dag.query, id, "File")} + return nil +} + +// Retrieves the name of the file. +func (r *File) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// FileSearchOpts contains options for File.Search +type FileSearchOpts struct { + // Interpret the pattern as a literal string instead of a regular expression. + Literal bool + // Enable searching across multiple lines. + Multiline bool + // Allow the . pattern to match newlines in multiline mode. + Dotall bool + // Enable case-insensitive matching. + Insensitive bool + // Honor .gitignore, .ignore, and .rgignore files. + SkipIgnored bool + // Skip hidden files (files starting with .). + SkipHidden bool + // Only return matching files, not lines and content + FilesOnly bool + // Limit the number of results to return + Limit int + + Paths []string + + Globs []string +} + +// Searches for content matching the given regular expression or literal string. +// +// Uses Rust regex syntax; escape literal ., [, ], {, }, | with backslashes. +func (r *File) Search(ctx context.Context, pattern string, opts ...FileSearchOpts) ([]SearchResult, error) { + q := r.query.Select("search") + for i := len(opts) - 1; i >= 0; i-- { + // `literal` optional argument + if !querybuilder.IsZeroValue(opts[i].Literal) { + q = q.Arg("literal", opts[i].Literal) + } + // `multiline` optional argument + if !querybuilder.IsZeroValue(opts[i].Multiline) { + q = q.Arg("multiline", opts[i].Multiline) + } + // `dotall` optional argument + if !querybuilder.IsZeroValue(opts[i].Dotall) { + q = q.Arg("dotall", opts[i].Dotall) + } + // `insensitive` optional argument + if !querybuilder.IsZeroValue(opts[i].Insensitive) { + q = q.Arg("insensitive", opts[i].Insensitive) + } + // `skipIgnored` optional argument + if !querybuilder.IsZeroValue(opts[i].SkipIgnored) { + q = q.Arg("skipIgnored", opts[i].SkipIgnored) + } + // `skipHidden` optional argument + if !querybuilder.IsZeroValue(opts[i].SkipHidden) { + q = q.Arg("skipHidden", opts[i].SkipHidden) + } + // `filesOnly` optional argument + if !querybuilder.IsZeroValue(opts[i].FilesOnly) { + q = q.Arg("filesOnly", opts[i].FilesOnly) + } + // `limit` optional argument + if !querybuilder.IsZeroValue(opts[i].Limit) { + q = q.Arg("limit", opts[i].Limit) + } + // `paths` optional argument + if !querybuilder.IsZeroValue(opts[i].Paths) { + q = q.Arg("paths", opts[i].Paths) + } + // `globs` optional argument + if !querybuilder.IsZeroValue(opts[i].Globs) { + q = q.Arg("globs", opts[i].Globs) + } + } + q = q.Arg("pattern", pattern) + + q = q.Select("id") + + type search struct { + Id ID + } + + convert := func(fields []search) []SearchResult { + out := []SearchResult{} + + for i := range fields { + val := SearchResult{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "SearchResult") + out = append(out, val) + } + + return out + } + var response []search + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// Retrieves the size of the file, in bytes. +func (r *File) Size(ctx context.Context) (int, error) { + if r.size != nil { + return *r.size, nil + } + q := r.query.Select("size") + + var response int + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Return file status +func (r *File) Stat(ctx context.Context) (*Stat, error) { + q := r.query.Select("stat") + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &Stat{ + query: selectNode(q.Root(), *objectID, "Stat"), + }, nil +} + +// Force evaluation in the engine. +func (r *File) Sync(ctx context.Context) (*File, error) { + q := r.query.Select("sync") + + var id ID + if err := q.Bind(&id).Execute(ctx); err != nil { + return nil, err + } + return &File{ + query: selectNode(q.Root(), id, "File"), + }, nil +} + +// Retrieves this file with its name set to the given name. +func (r *File) WithName(name string) *File { + q := r.query.Select("withName") + q = q.Arg("name", name) + + return &File{ + query: q, + } +} + +// FileWithReplacedOpts contains options for File.WithReplaced +type FileWithReplacedOpts struct { + // Replace all occurrences of the pattern. + All bool + // Replace the first match starting from the specified line. + FirstFrom int +} + +// Retrieves the file with content replaced with the given text. +// +// If 'all' is true, all occurrences of the pattern will be replaced. +// +// If 'firstAfter' is specified, only the first match starting at the specified line will be replaced. +// +// If neither are specified, and there are multiple matches for the pattern, this will error. +// +// If there are no matches for the pattern, this will error. +func (r *File) WithReplaced(search string, replacement string, opts ...FileWithReplacedOpts) *File { + q := r.query.Select("withReplaced") + for i := len(opts) - 1; i >= 0; i-- { + // `all` optional argument + if !querybuilder.IsZeroValue(opts[i].All) { + q = q.Arg("all", opts[i].All) + } + // `firstFrom` optional argument + if !querybuilder.IsZeroValue(opts[i].FirstFrom) { + q = q.Arg("firstFrom", opts[i].FirstFrom) + } + } + q = q.Arg("search", search) + q = q.Arg("replacement", replacement) + + return &File{ + query: q, + } +} + +// Retrieves this file with its created/modified timestamps set to the given time. +func (r *File) WithTimestamps(timestamp int) *File { + q := r.query.Select("withTimestamps") + q = q.Arg("timestamp", timestamp) + + return &File{ + query: q, + } +} + +// AsExportable returns this File as a Exportable. +// This is a local type conversion — no GraphQL call. +func (r *File) AsExportable() Exportable { + return &ExportableClient{ + query: r.query, + } +} + +// AsNode returns this File as a Node. +// This is a local type conversion — no GraphQL call. +func (r *File) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// AsSyncer returns this File as a Syncer. +// This is a local type conversion — no GraphQL call. +func (r *File) AsSyncer() Syncer { + return &SyncerClient{ + query: r.query, + } +} + +// Function represents a resolver provided by a Module. +// +// A function always evaluates against a parent object and is given a set of named arguments. +type Function struct { + query *querybuilder.Selection + + deprecated *string + description *string + id *ID + name *string + sourceModuleName *string +} +type WithFunctionFunc func(r *Function) *Function + +// With calls the provided function with current Function. +// +// This is useful for reusability and readability by not breaking the calling chain. +func (r *Function) With(f WithFunctionFunc) *Function { + return f(r) +} + +func (r *Function) WithGraphQLQuery(q *querybuilder.Selection) *Function { + return &Function{ + query: q, + } +} + +// Arguments accepted by the function, if any. +func (r *Function) Args(ctx context.Context) ([]FunctionArg, error) { + q := r.query.Select("args") + + q = q.Select("id") + + type args struct { + Id ID + } + + convert := func(fields []args) []FunctionArg { + out := []FunctionArg{} + + for i := range fields { + val := FunctionArg{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "FunctionArg") + out = append(out, val) + } + + return out + } + var response []args + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// The reason this function is deprecated, if any. +func (r *Function) Deprecated(ctx context.Context) (string, error) { + if r.deprecated != nil { + return *r.deprecated, nil + } + q := r.query.Select("deprecated") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A doc string for the function, if any. +func (r *Function) Description(ctx context.Context) (string, error) { + if r.description != nil { + return *r.description, nil + } + q := r.query.Select("description") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this Function. +func (r *Function) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Function) XXX_GraphQLType() string { + return "Function" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Function) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Function) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Function) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Function) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = Function{query: selectNode(dag.query, id, "Function")} + return nil +} + +// The name of the function. +func (r *Function) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The type returned by the function. +func (r *Function) ReturnType() *TypeDef { + q := r.query.Select("returnType") + + return &TypeDef{ + query: q, + } +} + +// The location of this function declaration. +func (r *Function) SourceMap(ctx context.Context) (*SourceMap, error) { + q := r.query.Select("sourceMap") + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &SourceMap{ + query: selectNode(q.Root(), *objectID, "SourceMap"), + }, nil +} + +// If this function is provided by a module, the name of the module. Unset otherwise. +func (r *Function) SourceModuleName(ctx context.Context) (string, error) { + if r.sourceModuleName != nil { + return *r.sourceModuleName, nil + } + q := r.query.Select("sourceModuleName") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Returns the function with a flag indicating it is an agent middleware. +func (r *Function) WithAgent() *Function { + q := r.query.Select("withAgent") + + return &Function{ + query: q, + } +} + +// FunctionWithArgOpts contains options for Function.WithArg +type FunctionWithArgOpts struct { + // A doc string for the argument, if any + Description string + // A default value to use for this argument if not explicitly set by the caller, if any + DefaultValue JSON + // If the argument is a Directory or File type, default to load path from context directory, relative to root directory. + DefaultPath string + // Patterns to ignore when loading the contextual argument value. + Ignore []string + // The source map for the argument definition. + SourceMap *SourceMap + // If deprecated, the reason or migration path. + Deprecated string + + DefaultAddress string +} + +// Returns the function with the provided argument +func (r *Function) WithArg(name string, typeDef *TypeDef, opts ...FunctionWithArgOpts) *Function { + assertNotNil("typeDef", typeDef) + q := r.query.Select("withArg") + for i := len(opts) - 1; i >= 0; i-- { + // `description` optional argument + if !querybuilder.IsZeroValue(opts[i].Description) { + q = q.Arg("description", opts[i].Description) + } + // `defaultValue` optional argument + if !querybuilder.IsZeroValue(opts[i].DefaultValue) { + q = q.Arg("defaultValue", opts[i].DefaultValue) + } + // `defaultPath` optional argument + if !querybuilder.IsZeroValue(opts[i].DefaultPath) { + q = q.Arg("defaultPath", opts[i].DefaultPath) + } + // `ignore` optional argument + if !querybuilder.IsZeroValue(opts[i].Ignore) { + q = q.Arg("ignore", opts[i].Ignore) + } + // `sourceMap` optional argument + if !querybuilder.IsZeroValue(opts[i].SourceMap) { + q = q.Arg("sourceMap", opts[i].SourceMap) + } + // `deprecated` optional argument + if !querybuilder.IsZeroValue(opts[i].Deprecated) { + q = q.Arg("deprecated", opts[i].Deprecated) + } + // `defaultAddress` optional argument + if !querybuilder.IsZeroValue(opts[i].DefaultAddress) { + q = q.Arg("defaultAddress", opts[i].DefaultAddress) + } + } + q = q.Arg("name", name) + q = q.Arg("typeDef", typeDef) + + return &Function{ + query: q, + } +} + +// FunctionWithCachePolicyOpts contains options for Function.WithCachePolicy +type FunctionWithCachePolicyOpts struct { + // The TTL for the cache policy, if applicable. Provided as a duration string, e.g. "5m", "1h30s". + TimeToLive string +} + +// Returns the function updated to use the provided cache policy. +func (r *Function) WithCachePolicy(policy FunctionCachePolicy, opts ...FunctionWithCachePolicyOpts) *Function { + q := r.query.Select("withCachePolicy") + for i := len(opts) - 1; i >= 0; i-- { + // `timeToLive` optional argument + if !querybuilder.IsZeroValue(opts[i].TimeToLive) { + q = q.Arg("timeToLive", opts[i].TimeToLive) + } + } + q = q.Arg("policy", policy) + + return &Function{ + query: q, + } +} + +// Returns the function with a flag indicating it's a check. +func (r *Function) WithCheck() *Function { + q := r.query.Select("withCheck") + + return &Function{ + query: q, + } +} + +// FunctionWithDeprecatedOpts contains options for Function.WithDeprecated +type FunctionWithDeprecatedOpts struct { + // Reason or migration path describing the deprecation. + Reason string +} + +// Returns the function with the provided deprecation reason. +func (r *Function) WithDeprecated(opts ...FunctionWithDeprecatedOpts) *Function { + q := r.query.Select("withDeprecated") + for i := len(opts) - 1; i >= 0; i-- { + // `reason` optional argument + if !querybuilder.IsZeroValue(opts[i].Reason) { + q = q.Arg("reason", opts[i].Reason) + } + } + + return &Function{ + query: q, + } +} + +// Returns the function with the given doc string. +func (r *Function) WithDescription(description string) *Function { + q := r.query.Select("withDescription") + q = q.Arg("description", description) + + return &Function{ + query: q, + } +} + +// Returns the function with a flag indicating it's a generator. +func (r *Function) WithGenerator() *Function { + q := r.query.Select("withGenerator") + + return &Function{ + query: q, + } +} + +// Returns the function with the given source map. +func (r *Function) WithSourceMap(sourceMap *SourceMap) *Function { + assertNotNil("sourceMap", sourceMap) + q := r.query.Select("withSourceMap") + q = q.Arg("sourceMap", sourceMap) + + return &Function{ + query: q, + } +} + +// Returns the function with a flag indicating it returns a service for dagger up. +func (r *Function) WithUp() *Function { + q := r.query.Select("withUp") + + return &Function{ + query: q, + } +} + +// AsNode returns this Function as a Node. +// This is a local type conversion — no GraphQL call. +func (r *Function) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// 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. +type FunctionArg struct { + query *querybuilder.Selection + + defaultAddress *string + defaultPath *string + defaultValue *JSON + deprecated *string + description *string + id *ID + name *string +} + +func (r *FunctionArg) WithGraphQLQuery(q *querybuilder.Selection) *FunctionArg { + return &FunctionArg{ + query: q, + } +} + +// Only applies to arguments of type Container. If the argument is not set, load it from the given address (e.g. alpine:latest) +func (r *FunctionArg) DefaultAddress(ctx context.Context) (string, error) { + if r.defaultAddress != nil { + return *r.defaultAddress, nil + } + q := r.query.Select("defaultAddress") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// 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 +func (r *FunctionArg) DefaultPath(ctx context.Context) (string, error) { + if r.defaultPath != nil { + return *r.defaultPath, nil + } + q := r.query.Select("defaultPath") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A default value to use for this argument when not explicitly set by the caller, if any. +func (r *FunctionArg) DefaultValue(ctx context.Context) (JSON, error) { + if r.defaultValue != nil { + return *r.defaultValue, nil + } + q := r.query.Select("defaultValue") + + var response JSON + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The reason this function is deprecated, if any. +func (r *FunctionArg) Deprecated(ctx context.Context) (string, error) { + if r.deprecated != nil { + return *r.deprecated, nil + } + q := r.query.Select("deprecated") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A doc string for the argument, if any. +func (r *FunctionArg) Description(ctx context.Context) (string, error) { + if r.description != nil { + return *r.description, nil + } + q := r.query.Select("description") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this FunctionArg. +func (r *FunctionArg) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *FunctionArg) XXX_GraphQLType() string { + return "FunctionArg" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *FunctionArg) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *FunctionArg) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *FunctionArg) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *FunctionArg) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = FunctionArg{query: selectNode(dag.query, id, "FunctionArg")} + return nil +} + +// 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. +func (r *FunctionArg) Ignore(ctx context.Context) ([]string, error) { + q := r.query.Select("ignore") + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The name of the argument in lowerCamelCase format. +func (r *FunctionArg) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The location of this arg declaration. +func (r *FunctionArg) SourceMap(ctx context.Context) (*SourceMap, error) { + q := r.query.Select("sourceMap") + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &SourceMap{ + query: selectNode(q.Root(), *objectID, "SourceMap"), + }, nil +} + +// The type of the argument. +func (r *FunctionArg) TypeDef() *TypeDef { + q := r.query.Select("typeDef") + + return &TypeDef{ + query: q, + } +} + +// AsNode returns this FunctionArg as a Node. +// This is a local type conversion — no GraphQL call. +func (r *FunctionArg) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// An active function call. +type FunctionCall struct { + query *querybuilder.Selection + + id *ID + name *string + parent *JSON + parentName *string + returnError *Void + returnValue *Void +} + +func (r *FunctionCall) WithGraphQLQuery(q *querybuilder.Selection) *FunctionCall { + return &FunctionCall{ + query: q, + } +} + +// A unique identifier for this FunctionCall. +func (r *FunctionCall) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *FunctionCall) XXX_GraphQLType() string { + return "FunctionCall" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *FunctionCall) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *FunctionCall) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *FunctionCall) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *FunctionCall) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = FunctionCall{query: selectNode(dag.query, id, "FunctionCall")} + return nil +} + +// The argument values the function is being invoked with. +func (r *FunctionCall) InputArgs(ctx context.Context) ([]FunctionCallArgValue, error) { + q := r.query.Select("inputArgs") + + q = q.Select("id") + + type inputArgs struct { + Id ID + } + + convert := func(fields []inputArgs) []FunctionCallArgValue { + out := []FunctionCallArgValue{} + + for i := range fields { + val := FunctionCallArgValue{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "FunctionCallArgValue") + out = append(out, val) + } + + return out + } + var response []inputArgs + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// The name of the function being called. +func (r *FunctionCall) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// 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. +func (r *FunctionCall) Parent(ctx context.Context) (JSON, error) { + if r.parent != nil { + return *r.parent, nil + } + q := r.query.Select("parent") + + var response JSON + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// 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. +func (r *FunctionCall) ParentName(ctx context.Context) (string, error) { + if r.parentName != nil { + return *r.parentName, nil + } + q := r.query.Select("parentName") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Return an error from the function. +func (r *FunctionCall) ReturnError(ctx context.Context, error *Error) error { + assertNotNil("error", error) + if r.returnError != nil { + return nil + } + q := r.query.Select("returnError") + q = q.Arg("error", error) + + return q.Execute(ctx) +} + +// Set the return value of the function call to the provided value. +func (r *FunctionCall) ReturnValue(ctx context.Context, value JSON) error { + if r.returnValue != nil { + return nil + } + q := r.query.Select("returnValue") + q = q.Arg("value", value) + + return q.Execute(ctx) +} + +// AsNode returns this FunctionCall as a Node. +// This is a local type conversion — no GraphQL call. +func (r *FunctionCall) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A value passed as a named argument to a function call. +type FunctionCallArgValue struct { + query *querybuilder.Selection + + id *ID + name *string + value *JSON +} + +func (r *FunctionCallArgValue) WithGraphQLQuery(q *querybuilder.Selection) *FunctionCallArgValue { + return &FunctionCallArgValue{ + query: q, + } +} + +// A unique identifier for this FunctionCallArgValue. +func (r *FunctionCallArgValue) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *FunctionCallArgValue) XXX_GraphQLType() string { + return "FunctionCallArgValue" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *FunctionCallArgValue) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *FunctionCallArgValue) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *FunctionCallArgValue) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *FunctionCallArgValue) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = FunctionCallArgValue{query: selectNode(dag.query, id, "FunctionCallArgValue")} + return nil +} + +// The name of the argument. +func (r *FunctionCallArgValue) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The value of the argument represented as a JSON serialized string. +func (r *FunctionCallArgValue) Value(ctx context.Context) (JSON, error) { + if r.value != nil { + return *r.value, nil + } + q := r.query.Select("value") + + var response JSON + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this FunctionCallArgValue as a Node. +// This is a local type conversion — no GraphQL call. +func (r *FunctionCallArgValue) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// The result of running an SDK's codegen. +type GeneratedCode struct { + query *querybuilder.Selection + + id *ID +} +type WithGeneratedCodeFunc func(r *GeneratedCode) *GeneratedCode + +// With calls the provided function with current GeneratedCode. +// +// This is useful for reusability and readability by not breaking the calling chain. +func (r *GeneratedCode) With(f WithGeneratedCodeFunc) *GeneratedCode { + return f(r) +} + +func (r *GeneratedCode) WithGraphQLQuery(q *querybuilder.Selection) *GeneratedCode { + return &GeneratedCode{ + query: q, + } +} + +// The directory containing the generated code. +func (r *GeneratedCode) Code() *Directory { + q := r.query.Select("code") + + return &Directory{ + query: q, + } +} + +// A unique identifier for this GeneratedCode. +func (r *GeneratedCode) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *GeneratedCode) XXX_GraphQLType() string { + return "GeneratedCode" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *GeneratedCode) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *GeneratedCode) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *GeneratedCode) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *GeneratedCode) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = GeneratedCode{query: selectNode(dag.query, id, "GeneratedCode")} + return nil +} + +// List of paths to mark generated in version control (i.e. .gitattributes). +func (r *GeneratedCode) VcsGeneratedPaths(ctx context.Context) ([]string, error) { + q := r.query.Select("vcsGeneratedPaths") + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// List of paths to ignore in version control (i.e. .gitignore). +func (r *GeneratedCode) VcsIgnoredPaths(ctx context.Context) ([]string, error) { + q := r.query.Select("vcsIgnoredPaths") + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Set the list of paths to mark generated in version control. +func (r *GeneratedCode) WithVCSGeneratedPaths(paths []string) *GeneratedCode { + q := r.query.Select("withVCSGeneratedPaths") + q = q.Arg("paths", paths) + + return &GeneratedCode{ + query: q, + } +} + +// Set the list of paths to ignore in version control. +func (r *GeneratedCode) WithVCSIgnoredPaths(paths []string) *GeneratedCode { + q := r.query.Select("withVCSIgnoredPaths") + q = q.Arg("paths", paths) + + return &GeneratedCode{ + query: q, + } +} + +// AsNode returns this GeneratedCode as a Node. +// This is a local type conversion — no GraphQL call. +func (r *GeneratedCode) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +type Generator struct { + query *querybuilder.Selection + + completed *bool + description *string + id *ID + isEmpty *bool + name *string +} +type WithGeneratorFunc func(r *Generator) *Generator + +// With calls the provided function with current Generator. +// +// This is useful for reusability and readability by not breaking the calling chain. +func (r *Generator) With(f WithGeneratorFunc) *Generator { + return f(r) +} + +func (r *Generator) WithGraphQLQuery(q *querybuilder.Selection) *Generator { + return &Generator{ + query: q, + } +} + +// The generated changeset from the last run +func (r *Generator) Changes() *Changeset { + q := r.query.Select("changes") + + return &Changeset{ + query: q, + } +} + +// Whether the generator complete +func (r *Generator) Completed(ctx context.Context) (bool, error) { + if r.completed != nil { + return *r.completed, nil + } + q := r.query.Select("completed") + + var response bool + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Return the description of the generator +func (r *Generator) Description(ctx context.Context) (string, error) { + if r.description != nil { + return *r.description, nil + } + q := r.query.Select("description") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this Generator. +func (r *Generator) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Generator) XXX_GraphQLType() string { + return "Generator" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Generator) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Generator) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Generator) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Generator) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = Generator{query: selectNode(dag.query, id, "Generator")} + return nil +} + +// Whether changeset from the last generator run is empty or not +func (r *Generator) IsEmpty(ctx context.Context) (bool, error) { + if r.isEmpty != nil { + return *r.isEmpty, nil + } + q := r.query.Select("isEmpty") + + var response bool + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Return the fully qualified name of the generator +func (r *Generator) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The original module in which the generator has been defined +func (r *Generator) OriginalModule() *Module { + q := r.query.Select("originalModule") + + return &Module{ + query: q, + } +} + +// The path of the generator within its module +func (r *Generator) Path(ctx context.Context) ([]string, error) { + q := r.query.Select("path") + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Execute the generator +func (r *Generator) Run() *Generator { + q := r.query.Select("run") + + return &Generator{ + query: q, + } +} + +// AsNode returns this Generator as a Node. +// This is a local type conversion — no GraphQL call. +func (r *Generator) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +type GeneratorGroup struct { + query *querybuilder.Selection + + id *ID + isEmpty *bool +} +type WithGeneratorGroupFunc func(r *GeneratorGroup) *GeneratorGroup + +// With calls the provided function with current GeneratorGroup. +// +// This is useful for reusability and readability by not breaking the calling chain. +func (r *GeneratorGroup) With(f WithGeneratorGroupFunc) *GeneratorGroup { + return f(r) +} + +func (r *GeneratorGroup) WithGraphQLQuery(q *querybuilder.Selection) *GeneratorGroup { + return &GeneratorGroup{ + query: q, + } +} + +// GeneratorGroupChangesOpts contains options for GeneratorGroup.Changes +type GeneratorGroupChangesOpts struct { + // Strategy to apply on conflicts between generators + // + // Default: FAIL_EARLY + OnConflict ChangesetsMergeConflict +} + +// The combined changes from the last run of the generators +// +// If any conflict occurs, for instance if the same file is modified by multiple generators, or if a file is both modified and deleted, an error is raised and the merge of the changesets will failed. +// +// Set 'continueOnConflicts' flag to force to merge the changes in a 'last write wins' strategy. +func (r *GeneratorGroup) Changes(opts ...GeneratorGroupChangesOpts) *Changeset { + q := r.query.Select("changes") + for i := len(opts) - 1; i >= 0; i-- { + // `onConflict` optional argument + if !querybuilder.IsZeroValue(opts[i].OnConflict) { + q = q.Arg("onConflict", opts[i].OnConflict) + } + } + + return &Changeset{ + query: q, + } +} + +// A unique identifier for this GeneratorGroup. +func (r *GeneratorGroup) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *GeneratorGroup) XXX_GraphQLType() string { + return "GeneratorGroup" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *GeneratorGroup) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *GeneratorGroup) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *GeneratorGroup) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *GeneratorGroup) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = GeneratorGroup{query: selectNode(dag.query, id, "GeneratorGroup")} + return nil +} + +// Whether the generated changeset from the last run is empty or not +func (r *GeneratorGroup) IsEmpty(ctx context.Context) (bool, error) { + if r.isEmpty != nil { + return *r.isEmpty, nil + } + q := r.query.Select("isEmpty") + + var response bool + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Return a list of individual generators and their details +func (r *GeneratorGroup) List(ctx context.Context) ([]Generator, error) { + q := r.query.Select("list") + + q = q.Select("id") + + type list struct { + Id ID + } + + convert := func(fields []list) []Generator { + out := []Generator{} + + for i := range fields { + val := Generator{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "Generator") + out = append(out, val) + } + + return out + } + var response []list + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// Load failures tolerated while collecting the generators. +// +// Empty unless a workspace module could not be loaded during an unscoped 'dagger generate' (no selector), where load failures are tolerated so the modules that do load still generate. Each entry is a human-readable error message. An explicit selector keeps failing hard instead. +func (r *GeneratorGroup) LoadFailures(ctx context.Context) ([]string, error) { + q := r.query.Select("loadFailures") + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Execute all selected generators +func (r *GeneratorGroup) Run() *GeneratorGroup { + q := r.query.Select("run") + + return &GeneratorGroup{ + query: q, + } +} + +// AsNode returns this GeneratorGroup as a Node. +// This is a local type conversion — no GraphQL call. +func (r *GeneratorGroup) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// An immutable git commit. +type GitCommit struct { + query *querybuilder.Selection + + authorEmail *string + authorName *string + authoredDate *string + committedDate *string + committerEmail *string + committerName *string + id *ID + message *string + messageBody *string + messageHeadline *string + sha *string + shortSha *string +} + +func (r *GitCommit) WithGraphQLQuery(q *querybuilder.Selection) *GitCommit { + return &GitCommit{ + query: q, + } +} + +// GitCommitAncestorReleaseTagOpts contains options for GitCommit.AncestorReleaseTag +type GitCommitAncestorReleaseTagOpts struct { + // Include pre-release tags when choosing the latest tag. + IncludePreRelease bool +} + +// The latest semver release tag reachable from this commit. +func (r *GitCommit) AncestorReleaseTag(ctx context.Context, opts ...GitCommitAncestorReleaseTagOpts) (*GitRef, error) { + q := r.query.Select("ancestorReleaseTag") + for i := len(opts) - 1; i >= 0; i-- { + // `includePreRelease` optional argument + if !querybuilder.IsZeroValue(opts[i].IncludePreRelease) { + q = q.Arg("includePreRelease", opts[i].IncludePreRelease) + } + } + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &GitRef{ + query: selectNode(q.Root(), *objectID, "GitRef"), + }, nil +} + +// Git author email. +func (r *GitCommit) AuthorEmail(ctx context.Context) (string, error) { + if r.authorEmail != nil { + return *r.authorEmail, nil + } + q := r.query.Select("authorEmail") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Git author name. +func (r *GitCommit) AuthorName(ctx context.Context) (string, error) { + if r.authorName != nil { + return *r.authorName, nil + } + q := r.query.Select("authorName") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Git author date, in RFC3339 format. +func (r *GitCommit) AuthoredDate(ctx context.Context) (string, error) { + if r.authoredDate != nil { + return *r.authoredDate, nil + } + q := r.query.Select("authoredDate") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Git committer date, in RFC3339 format. +func (r *GitCommit) CommittedDate(ctx context.Context) (string, error) { + if r.committedDate != nil { + return *r.committedDate, nil + } + q := r.query.Select("committedDate") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Git committer email. +func (r *GitCommit) CommitterEmail(ctx context.Context) (string, error) { + if r.committerEmail != nil { + return *r.committerEmail, nil + } + q := r.query.Select("committerEmail") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Git committer name. +func (r *GitCommit) CommitterName(ctx context.Context) (string, error) { + if r.committerName != nil { + return *r.committerName, nil + } + q := r.query.Select("committerName") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this GitCommit. +func (r *GitCommit) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *GitCommit) XXX_GraphQLType() string { + return "GitCommit" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *GitCommit) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *GitCommit) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *GitCommit) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *GitCommit) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = GitCommit{query: selectNode(dag.query, id, "GitCommit")} + return nil +} + +// Full commit message. +func (r *GitCommit) Message(ctx context.Context) (string, error) { + if r.message != nil { + return *r.message, nil + } + q := r.query.Select("message") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Commit message body, excluding the headline. +func (r *GitCommit) MessageBody(ctx context.Context) (string, error) { + if r.messageBody != nil { + return *r.messageBody, nil + } + q := r.query.Select("messageBody") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// First line of the commit message. +func (r *GitCommit) MessageHeadline(ctx context.Context) (string, error) { + if r.messageHeadline != nil { + return *r.messageHeadline, nil + } + q := r.query.Select("messageHeadline") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Parent commit SHAs. +func (r *GitCommit) ParentShas(ctx context.Context) ([]string, error) { + q := r.query.Select("parentShas") + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// GitCommitReleaseTagOpts contains options for GitCommit.ReleaseTag +type GitCommitReleaseTagOpts struct { + // Include pre-release tags when choosing the latest tag. + IncludePreRelease bool +} + +// The latest semver release tag that points directly at this commit. +func (r *GitCommit) ReleaseTag(ctx context.Context, opts ...GitCommitReleaseTagOpts) (*GitRef, error) { + q := r.query.Select("releaseTag") + for i := len(opts) - 1; i >= 0; i-- { + // `includePreRelease` optional argument + if !querybuilder.IsZeroValue(opts[i].IncludePreRelease) { + q = q.Arg("includePreRelease", opts[i].IncludePreRelease) + } + } + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &GitRef{ + query: selectNode(q.Root(), *objectID, "GitRef"), + }, nil +} + +// The full commit SHA. +func (r *GitCommit) Sha(ctx context.Context) (string, error) { + if r.sha != nil { + return *r.sha, nil + } + q := r.query.Select("sha") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The abbreviated commit SHA. +func (r *GitCommit) ShortSha(ctx context.Context) (string, error) { + if r.shortSha != nil { + return *r.shortSha, nil + } + q := r.query.Select("shortSha") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// GitCommitTreeOpts contains options for GitCommit.Tree +type GitCommitTreeOpts struct { + // Set to true to discard .git directory. + DiscardGitDir bool + // The depth of the tree to fetch. + // + // Default: 1 + Depth int + // Set to true to populate tag refs in the local checkout .git. + IncludeTags bool +} + +// The filesystem tree at this commit. +func (r *GitCommit) Tree(opts ...GitCommitTreeOpts) *Directory { + q := r.query.Select("tree") + for i := len(opts) - 1; i >= 0; i-- { + // `discardGitDir` optional argument + if !querybuilder.IsZeroValue(opts[i].DiscardGitDir) { + q = q.Arg("discardGitDir", opts[i].DiscardGitDir) + } + // `depth` optional argument + if !querybuilder.IsZeroValue(opts[i].Depth) { + q = q.Arg("depth", opts[i].Depth) + } + // `includeTags` optional argument + if !querybuilder.IsZeroValue(opts[i].IncludeTags) { + q = q.Arg("includeTags", opts[i].IncludeTags) + } + } + + return &Directory{ + query: q, + } +} + +// AsNode returns this GitCommit as a Node. +// This is a local type conversion — no GraphQL call. +func (r *GitCommit) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A git ref (tag, branch, or commit). +type GitRef struct { + query *querybuilder.Selection + + commit *string + commitSHA *string + id *ID + name *string + ref *string +} +type WithGitRefFunc func(r *GitRef) *GitRef + +// With calls the provided function with current GitRef. +// +// This is useful for reusability and readability by not breaking the calling chain. +func (r *GitRef) With(f WithGitRefFunc) *GitRef { + return f(r) +} + +func (r *GitRef) WithGraphQLQuery(q *querybuilder.Selection) *GitRef { + return &GitRef{ + query: q, + } +} + +// GitRefAsWorkspaceOpts contains options for GitRef.AsWorkspace +type GitRefAsWorkspaceOpts struct { + // Current working directory inside the workspace root. Defaults to the workspace root. + // + // Default: "/" + Cwd string +} + +// Creates a synthetic workspace from this git ref. +func (r *GitRef) AsWorkspace(opts ...GitRefAsWorkspaceOpts) *Workspace { + q := r.query.Select("asWorkspace") + for i := len(opts) - 1; i >= 0; i-- { + // `cwd` optional argument + if !querybuilder.IsZeroValue(opts[i].Cwd) { + q = q.Arg("cwd", opts[i].Cwd) + } + } + + return &Workspace{ + query: q, + } +} + +// The resolved commit id at this ref. +// +// Deprecated: Use "commitSHA" instead. +func (r *GitRef) Commit(ctx context.Context) (string, error) { + if r.commit != nil { + return *r.commit, nil + } + q := r.query.Select("commit") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The resolved commit SHA at this ref. +func (r *GitRef) CommitSHA(ctx context.Context) (string, error) { + if r.commitSHA != nil { + return *r.commitSHA, nil + } + q := r.query.Select("commitSHA") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Find the best common ancestor between this ref and another ref. +func (r *GitRef) CommonAncestor(other *GitRef) *GitRef { + assertNotNil("other", other) + q := r.query.Select("commonAncestor") + q = q.Arg("other", other) + + return &GitRef{ + query: q, + } +} + +// A unique identifier for this GitRef. +func (r *GitRef) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *GitRef) XXX_GraphQLType() string { + return "GitRef" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *GitRef) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *GitRef) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *GitRef) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *GitRef) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = GitRef{query: selectNode(dag.query, id, "GitRef")} + return nil +} + +// GitRefLogOpts contains options for GitRef.Log +type GitRefLogOpts struct { + // Maximum number of commits to return. + // + // Default: 10 + Limit int + // Only include commits touching these paths, relative to the root of the repository. + Paths []string + // Exclude commits reachable from this ref, i.e. only list commits added on top of it. + Base *GitRef +} + +// Commits reachable from this ref, newest first, starting with the commit this ref resolves to. +func (r *GitRef) Log(ctx context.Context, opts ...GitRefLogOpts) ([]GitCommit, error) { + q := r.query.Select("log") + for i := len(opts) - 1; i >= 0; i-- { + // `limit` optional argument + if !querybuilder.IsZeroValue(opts[i].Limit) { + q = q.Arg("limit", opts[i].Limit) + } + // `paths` optional argument + if !querybuilder.IsZeroValue(opts[i].Paths) { + q = q.Arg("paths", opts[i].Paths) + } + // `base` optional argument + if !querybuilder.IsZeroValue(opts[i].Base) { + q = q.Arg("base", opts[i].Base) + } + } + + q = q.Select("id") + + type log struct { + Id ID + } + + convert := func(fields []log) []GitCommit { + out := []GitCommit{} + + for i := range fields { + val := GitCommit{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "GitCommit") + out = append(out, val) + } + + return out + } + var response []log + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// The resolved name of this ref. +func (r *GitRef) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The resolved ref name at this ref. +// +// Deprecated: Use "name" instead. +func (r *GitRef) Ref(ctx context.Context) (string, error) { + if r.ref != nil { + return *r.ref, nil + } + q := r.query.Select("ref") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The commit this ref resolves to. +func (r *GitRef) TargetCommit() *GitCommit { + q := r.query.Select("targetCommit") + + return &GitCommit{ + query: q, + } +} + +// GitRefTreeOpts contains options for GitRef.Tree +type GitRefTreeOpts struct { + // Set to true to discard .git directory. + DiscardGitDir bool + // The depth of the tree to fetch. + // + // Default: 1 + Depth int + // Set to true to populate tag refs in the local checkout .git. + IncludeTags bool +} + +// The filesystem tree at this ref. +func (r *GitRef) Tree(opts ...GitRefTreeOpts) *Directory { + q := r.query.Select("tree") + for i := len(opts) - 1; i >= 0; i-- { + // `discardGitDir` optional argument + if !querybuilder.IsZeroValue(opts[i].DiscardGitDir) { + q = q.Arg("discardGitDir", opts[i].DiscardGitDir) + } + // `depth` optional argument + if !querybuilder.IsZeroValue(opts[i].Depth) { + q = q.Arg("depth", opts[i].Depth) + } + // `includeTags` optional argument + if !querybuilder.IsZeroValue(opts[i].IncludeTags) { + q = q.Arg("includeTags", opts[i].IncludeTags) + } + } + + return &Directory{ + query: q, + } +} + +// AsNode returns this GitRef as a Node. +// This is a local type conversion — no GraphQL call. +func (r *GitRef) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A git repository. +type GitRepository struct { + query *querybuilder.Selection + + id *ID + url *string +} + +func (r *GitRepository) WithGraphQLQuery(q *querybuilder.Selection) *GitRepository { + return &GitRepository{ + query: q, + } +} + +// GitRepositoryAsWorkspaceOpts contains options for GitRepository.AsWorkspace +type GitRepositoryAsWorkspaceOpts struct { + // Current working directory inside the workspace root. Defaults to the workspace root. + // + // Default: "/" + Cwd string +} + +// Creates a synthetic workspace from this git repository. +func (r *GitRepository) AsWorkspace(opts ...GitRepositoryAsWorkspaceOpts) *Workspace { + q := r.query.Select("asWorkspace") + for i := len(opts) - 1; i >= 0; i-- { + // `cwd` optional argument + if !querybuilder.IsZeroValue(opts[i].Cwd) { + q = q.Arg("cwd", opts[i].Cwd) + } + } + + return &Workspace{ + query: q, + } +} + +// Returns details of a branch. +func (r *GitRepository) Branch(name string) *GitRef { + q := r.query.Select("branch") + q = q.Arg("name", name) + + return &GitRef{ + query: q, + } +} + +// GitRepositoryBranchesOpts contains options for GitRepository.Branches +type GitRepositoryBranchesOpts struct { + // Glob patterns (e.g., "refs/tags/v*"). + Patterns []string +} + +// branches that match any of the given glob patterns. +func (r *GitRepository) Branches(ctx context.Context, opts ...GitRepositoryBranchesOpts) ([]string, error) { + q := r.query.Select("branches") + for i := len(opts) - 1; i >= 0; i-- { + // `patterns` optional argument + if !querybuilder.IsZeroValue(opts[i].Patterns) { + q = q.Arg("patterns", opts[i].Patterns) + } + } + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Returns details of a commit. +func (r *GitRepository) Commit(id string) *GitCommit { + q := r.query.Select("commit") + q = q.Arg("id", id) + + return &GitCommit{ + query: q, + } +} + +// Returns details for HEAD. +func (r *GitRepository) Head() *GitRef { + q := r.query.Select("head") + + return &GitRef{ + query: q, + } +} + +// A unique identifier for this GitRepository. +func (r *GitRepository) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *GitRepository) XXX_GraphQLType() string { + return "GitRepository" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *GitRepository) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *GitRepository) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *GitRepository) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *GitRepository) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = GitRepository{query: selectNode(dag.query, id, "GitRepository")} + return nil +} + +// Returns details for the latest semver tag. +func (r *GitRepository) LatestVersion() *GitRef { + q := r.query.Select("latestVersion") + + return &GitRef{ + query: q, + } +} + +// Returns details of a ref. +func (r *GitRepository) Ref(name string) *GitRef { + q := r.query.Select("ref") + q = q.Arg("name", name) + + return &GitRef{ + query: q, + } +} + +// Returns details of a tag. +func (r *GitRepository) Tag(name string) *GitRef { + q := r.query.Select("tag") + q = q.Arg("name", name) + + return &GitRef{ + query: q, + } +} + +// GitRepositoryTagsOpts contains options for GitRepository.Tags +type GitRepositoryTagsOpts struct { + // Glob patterns (e.g., "refs/tags/v*"). + Patterns []string +} + +// tags that match any of the given glob patterns. +func (r *GitRepository) Tags(ctx context.Context, opts ...GitRepositoryTagsOpts) ([]string, error) { + q := r.query.Select("tags") + for i := len(opts) - 1; i >= 0; i-- { + // `patterns` optional argument + if !querybuilder.IsZeroValue(opts[i].Patterns) { + q = q.Arg("patterns", opts[i].Patterns) + } + } + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Returns the changeset of uncommitted changes in the git repository. +func (r *GitRepository) Uncommitted() *Changeset { + q := r.query.Select("uncommitted") + + return &Changeset{ + query: q, + } +} + +// The URL of the git repository. +func (r *GitRepository) URL(ctx context.Context) (string, error) { + if r.url != nil { + return *r.url, nil + } + q := r.query.Select("url") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this GitRepository as a Node. +// This is a local type conversion — no GraphQL call. +func (r *GitRepository) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// An internal persistent HTTP state. +type HTTPState struct { + query *querybuilder.Selection + + id *ID +} + +func (r *HTTPState) WithGraphQLQuery(q *querybuilder.Selection) *HTTPState { + return &HTTPState{ + query: q, + } +} + +// A unique identifier for this HTTPState. +func (r *HTTPState) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *HTTPState) XXX_GraphQLType() string { + return "HTTPState" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *HTTPState) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *HTTPState) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *HTTPState) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *HTTPState) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = HTTPState{query: selectNode(dag.query, id, "HTTPState")} + return nil +} + +// AsNode returns this HTTPState as a Node. +// This is a local type conversion — no GraphQL call. +func (r *HTTPState) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// Image healthcheck configuration. +type HealthcheckConfig struct { + query *querybuilder.Selection + + id *ID + interval *string + retries *int + shell *bool + startInterval *string + startPeriod *string + timeout *string +} + +func (r *HealthcheckConfig) WithGraphQLQuery(q *querybuilder.Selection) *HealthcheckConfig { + return &HealthcheckConfig{ + query: q, + } +} + +// Healthcheck command arguments. +func (r *HealthcheckConfig) Args(ctx context.Context) ([]string, error) { + q := r.query.Select("args") + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this HealthcheckConfig. +func (r *HealthcheckConfig) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *HealthcheckConfig) XXX_GraphQLType() string { + return "HealthcheckConfig" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *HealthcheckConfig) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *HealthcheckConfig) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *HealthcheckConfig) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *HealthcheckConfig) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = HealthcheckConfig{query: selectNode(dag.query, id, "HealthcheckConfig")} + return nil +} + +// Interval between running healthcheck. Example:30s +func (r *HealthcheckConfig) Interval(ctx context.Context) (string, error) { + if r.interval != nil { + return *r.interval, nil + } + q := r.query.Select("interval") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The maximum number of consecutive failures before the container is marked as unhealthy. Example:3 +func (r *HealthcheckConfig) Retries(ctx context.Context) (int, error) { + if r.retries != nil { + return *r.retries, nil + } + q := r.query.Select("retries") + + var response int + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Healthcheck command is a shell command. +func (r *HealthcheckConfig) Shell(ctx context.Context) (bool, error) { + if r.shell != nil { + return *r.shell, nil + } + q := r.query.Select("shell") + + var response bool + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// StartInterval configures the duration between checks during the startup phase. Example:5s +func (r *HealthcheckConfig) StartInterval(ctx context.Context) (string, error) { + if r.startInterval != nil { + return *r.startInterval, nil + } + q := r.query.Select("startInterval") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// StartPeriod allows for failures during this initial startup period which do not count towards maximum number of retries. Example:0s +func (r *HealthcheckConfig) StartPeriod(ctx context.Context) (string, error) { + if r.startPeriod != nil { + return *r.startPeriod, nil + } + q := r.query.Select("startPeriod") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Healthcheck timeout. Example:3s +func (r *HealthcheckConfig) Timeout(ctx context.Context) (string, error) { + if r.timeout != nil { + return *r.timeout, nil + } + q := r.query.Select("timeout") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this HealthcheckConfig as a Node. +// This is a local type conversion — no GraphQL call. +func (r *HealthcheckConfig) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// 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. +type InputTypeDef struct { + query *querybuilder.Selection + + id *ID + name *string +} + +func (r *InputTypeDef) WithGraphQLQuery(q *querybuilder.Selection) *InputTypeDef { + return &InputTypeDef{ + query: q, + } +} + +// Static fields defined on this input object, if any. +func (r *InputTypeDef) Fields(ctx context.Context) ([]FieldTypeDef, error) { + q := r.query.Select("fields") + + q = q.Select("id") + + type fields struct { + Id ID + } + + convert := func(fields []fields) []FieldTypeDef { + out := []FieldTypeDef{} + + for i := range fields { + val := FieldTypeDef{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "FieldTypeDef") + out = append(out, val) + } + + return out + } + var response []fields + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// A unique identifier for this InputTypeDef. +func (r *InputTypeDef) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *InputTypeDef) XXX_GraphQLType() string { + return "InputTypeDef" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *InputTypeDef) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *InputTypeDef) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *InputTypeDef) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *InputTypeDef) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = InputTypeDef{query: selectNode(dag.query, id, "InputTypeDef")} + return nil +} + +// The name of the input object. +func (r *InputTypeDef) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this InputTypeDef as a Node. +// This is a local type conversion — no GraphQL call. +func (r *InputTypeDef) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A definition of a custom interface defined in a Module. +type InterfaceTypeDef struct { + query *querybuilder.Selection + + description *string + id *ID + name *string + sourceModuleName *string +} + +func (r *InterfaceTypeDef) WithGraphQLQuery(q *querybuilder.Selection) *InterfaceTypeDef { + return &InterfaceTypeDef{ + query: q, + } +} + +// The doc string for the interface, if any. +func (r *InterfaceTypeDef) Description(ctx context.Context) (string, error) { + if r.description != nil { + return *r.description, nil + } + q := r.query.Select("description") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Functions defined on this interface, if any. +func (r *InterfaceTypeDef) Functions(ctx context.Context) ([]Function, error) { + q := r.query.Select("functions") + + q = q.Select("id") + + type functions struct { + Id ID + } + + convert := func(fields []functions) []Function { + out := []Function{} + + for i := range fields { + val := Function{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "Function") + out = append(out, val) + } + + return out + } + var response []functions + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// A unique identifier for this InterfaceTypeDef. +func (r *InterfaceTypeDef) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *InterfaceTypeDef) XXX_GraphQLType() string { + return "InterfaceTypeDef" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *InterfaceTypeDef) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *InterfaceTypeDef) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *InterfaceTypeDef) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *InterfaceTypeDef) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = InterfaceTypeDef{query: selectNode(dag.query, id, "InterfaceTypeDef")} + return nil +} + +// The name of the interface. +func (r *InterfaceTypeDef) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The location of this interface declaration. +func (r *InterfaceTypeDef) SourceMap(ctx context.Context) (*SourceMap, error) { + q := r.query.Select("sourceMap") + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &SourceMap{ + query: selectNode(q.Root(), *objectID, "SourceMap"), + }, nil +} + +// If this InterfaceTypeDef is associated with a Module, the name of the module. Unset otherwise. +func (r *InterfaceTypeDef) SourceModuleName(ctx context.Context) (string, error) { + if r.sourceModuleName != nil { + return *r.sourceModuleName, nil + } + q := r.query.Select("sourceModuleName") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this InterfaceTypeDef as a Node. +// This is a local type conversion — no GraphQL call. +func (r *InterfaceTypeDef) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +type JSONValue struct { + query *querybuilder.Selection + + asBoolean *bool + asInteger *int + asString *string + contents *JSON + id *ID +} +type WithJSONValueFunc func(r *JSONValue) *JSONValue + +// With calls the provided function with current JSONValue. +// +// This is useful for reusability and readability by not breaking the calling chain. +func (r *JSONValue) With(f WithJSONValueFunc) *JSONValue { + return f(r) +} + +func (r *JSONValue) WithGraphQLQuery(q *querybuilder.Selection) *JSONValue { + return &JSONValue{ + query: q, + } +} + +// Decode an array from json +func (r *JSONValue) AsArray(ctx context.Context) ([]JSONValue, error) { + q := r.query.Select("asArray") + + q = q.Select("id") + + type asArray struct { + Id ID + } + + convert := func(fields []asArray) []JSONValue { + out := []JSONValue{} + + for i := range fields { + val := JSONValue{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "JSONValue") + out = append(out, val) + } + + return out + } + var response []asArray + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// Decode a boolean from json +func (r *JSONValue) AsBoolean(ctx context.Context) (bool, error) { + if r.asBoolean != nil { + return *r.asBoolean, nil + } + q := r.query.Select("asBoolean") + + var response bool + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Decode an integer from json +func (r *JSONValue) AsInteger(ctx context.Context) (int, error) { + if r.asInteger != nil { + return *r.asInteger, nil + } + q := r.query.Select("asInteger") + + var response int + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Decode a string from json +func (r *JSONValue) AsString(ctx context.Context) (string, error) { + if r.asString != nil { + return *r.asString, nil + } + q := r.query.Select("asString") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// JSONValueContentsOpts contains options for JSONValue.Contents +type JSONValueContentsOpts struct { + // Pretty-print + Pretty bool + // Optional line prefix + // + // Default: " " + Indent string +} + +// Return the value encoded as json +func (r *JSONValue) Contents(ctx context.Context, opts ...JSONValueContentsOpts) (JSON, error) { + if r.contents != nil { + return *r.contents, nil + } + q := r.query.Select("contents") + for i := len(opts) - 1; i >= 0; i-- { + // `pretty` optional argument + if !querybuilder.IsZeroValue(opts[i].Pretty) { + q = q.Arg("pretty", opts[i].Pretty) + } + // `indent` optional argument + if !querybuilder.IsZeroValue(opts[i].Indent) { + q = q.Arg("indent", opts[i].Indent) + } + } + + var response JSON + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Lookup the field at the given path, and return its value. +func (r *JSONValue) Field(path []string) *JSONValue { + q := r.query.Select("field") + q = q.Arg("path", path) + + return &JSONValue{ + query: q, + } +} + +// List fields of the encoded object +func (r *JSONValue) Fields(ctx context.Context) ([]string, error) { + q := r.query.Select("fields") + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this JSONValue. +func (r *JSONValue) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *JSONValue) XXX_GraphQLType() string { + return "JSONValue" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *JSONValue) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *JSONValue) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *JSONValue) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *JSONValue) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = JSONValue{query: selectNode(dag.query, id, "JSONValue")} + return nil +} + +// Encode a boolean to json +func (r *JSONValue) NewBoolean(value bool) *JSONValue { + q := r.query.Select("newBoolean") + q = q.Arg("value", value) + + return &JSONValue{ + query: q, + } +} + +// Encode an integer to json +func (r *JSONValue) NewInteger(value int) *JSONValue { + q := r.query.Select("newInteger") + q = q.Arg("value", value) + + return &JSONValue{ + query: q, + } +} + +// Encode a string to json +func (r *JSONValue) NewString(value string) *JSONValue { + q := r.query.Select("newString") + q = q.Arg("value", value) + + return &JSONValue{ + query: q, + } +} + +// Return a new json value, decoded from the given content +func (r *JSONValue) WithContents(contents JSON) *JSONValue { + q := r.query.Select("withContents") + q = q.Arg("contents", contents) + + return &JSONValue{ + query: q, + } +} + +// Set a new field at the given path +func (r *JSONValue) WithField(path []string, value *JSONValue) *JSONValue { + assertNotNil("value", value) + q := r.query.Select("withField") + q = q.Arg("path", path) + q = q.Arg("value", value) + + return &JSONValue{ + query: q, + } +} + +// AsNode returns this JSONValue as a Node. +// This is a local type conversion — no GraphQL call. +func (r *JSONValue) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A conversation with a large language model (LLM): queue prompts, expose tools, and step the model until it completes its turn. +type LLM struct { + query *querybuilder.Selection + + contextTokens *int + contextWindow *int + hasPending *bool + id *ID + lastReply *string + model *string + portableID *ID + provider *string + reasoningEffort *string + replay *ID + sync *ID + tools *string + transcript *string +} +type WithLLMFunc func(r *LLM) *LLM + +// With calls the provided function with current LLM. +// +// This is useful for reusability and readability by not breaking the calling chain. +func (r *LLM) With(f WithLLMFunc) *LLM { + return f(r) +} + +func (r *LLM) WithGraphQLQuery(q *querybuilder.Selection) *LLM { + return &LLM{ + query: q, + } +} + +// estimated number of tokens currently occupying the context window; unlike tokenUsage this is not cumulative over the session +func (r *LLM) ContextTokens(ctx context.Context) (int, error) { + if r.contextTokens != nil { + return *r.contextTokens, nil + } + q := r.query.Select("contextTokens") + + var response int + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The model's total context window in tokens, or null if unknown (e.g. a local or uncatalogued model). +func (r *LLM) ContextWindow(ctx context.Context) (int, error) { + if r.contextWindow != nil { + return *r.contextWindow, nil + } + q := r.query.Select("contextWindow") + + var response int + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Fork the conversation, so that otherwise-identical follow-ups evaluate independently instead of deduplicating to a single cached result. +func (r *LLM) Fork(label string) *LLM { + q := r.query.Select("fork") + q = q.Arg("label", label) + + return &LLM{ + query: q, + } +} + +// Report whether anything is queued to send to the model: an unsent prompt or unevaluated tool results. When true, another step will do work; when false, the turn is complete. +func (r *LLM) HasPending(ctx context.Context) (bool, error) { + if r.hasPending != nil { + return *r.hasPending, nil + } + q := r.query.Select("hasPending") + + var response bool + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this LLM. +func (r *LLM) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *LLM) XXX_GraphQLType() string { + return "LLM" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *LLM) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *LLM) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *LLM) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *LLM) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = LLM{query: selectNode(dag.query, id, "LLM")} + return nil +} + +// The text of the model's most recent reply. +func (r *LLM) LastReply(ctx context.Context) (string, error) { + if r.lastReply != nil { + return *r.lastReply, nil + } + q := r.query.Select("lastReply") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// LLMLoopOpts contains options for LLM.Loop +type LLMLoopOpts struct { + // Cap the number of steps. The loop fails if the cap is reached before the model ends its turn. + MaxSteps int + // Cap the model's output tokens on each step. Defaults to the model's maximum. + MaxTokens int +} + +// Send the queued prompt and step the model against the available tools, until it ends its turn: a reply with no tool calls and nothing left queued. +func (r *LLM) Loop(opts ...LLMLoopOpts) *LLM { + q := r.query.Select("loop") + for i := len(opts) - 1; i >= 0; i-- { + // `maxSteps` optional argument + if !querybuilder.IsZeroValue(opts[i].MaxSteps) { + q = q.Arg("maxSteps", opts[i].MaxSteps) + } + // `maxTokens` optional argument + if !querybuilder.IsZeroValue(opts[i].MaxTokens) { + q = q.Arg("maxTokens", opts[i].MaxTokens) + } + } + + return &LLM{ + query: q, + } +} + +// The full message history, as structured messages. +func (r *LLM) Messages(ctx context.Context) ([]LLMMessage, error) { + q := r.query.Select("messages") + + q = q.Select("id") + + type messages struct { + Id ID + } + + convert := func(fields []messages) []LLMMessage { + out := []LLMMessage{} + + for i := range fields { + val := LLMMessage{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "LLMMessage") + out = append(out, val) + } + + return out + } + var response []messages + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// The model the conversation is running against, after resolving any configured default. +func (r *LLM) Model(ctx context.Context) (string, error) { + if r.model != nil { + return *r.model, nil + } + q := r.query.Select("model") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A portable, self-contained ID for the conversation that node() can resolve in any session. Unlike id, which may return an engine-local runtime handle valid only within the current session, this returns the recipe form suitable for persisting and later restoring the conversation. The recipe is flattened: bindings superseded during the session (workspace overlays recorded by each mutating tool call, and re-bound toolsets) are dropped, while the current workspace binding — including any pending, un-exported edits — is preserved. +func (r *LLM) PortableID(ctx context.Context) (ID, error) { + if r.portableID != nil { + return *r.portableID, nil + } + q := r.query.Select("portableID") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The provider serving the model, e.g. "anthropic", "openai", "google", or "local". +func (r *LLM) Provider(ctx context.Context) (string, error) { + if r.provider != nil { + return *r.provider, nil + } + q := r.query.Select("provider") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The reasoning effort in use, e.g. "low", "medium", or "high". Empty or "none" when reasoning is disabled. +func (r *LLM) ReasoningEffort(ctx context.Context) (string, error) { + if r.reasoningEffort != nil { + return *r.reasoningEffort, nil + } + q := r.query.Select("reasoningEffort") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Re-emit telemetry spans for the full message history, so a loaded conversation displays in the TUI. +func (r *LLM) Replay(ctx context.Context) (*LLM, error) { + q := r.query.Select("replay") + + var id ID + if err := q.Bind(&id).Execute(ctx); err != nil { + return nil, err + } + return &LLM{ + query: selectNode(q.Root(), id, "LLM"), + }, nil +} + +// The skills visible to the model, exactly as the ListSkills tool serves them: engine-embedded skills, skills installed with withSkills, and skills discovered in the workspace. +func (r *LLM) Skills(ctx context.Context) ([]LLMSkill, error) { + q := r.query.Select("skills") + + q = q.Select("id") + + type skills struct { + Id ID + } + + convert := func(fields []skills) []LLMSkill { + out := []LLMSkill{} + + for i := range fields { + val := LLMSkill{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "LLMSkill") + out = append(out, val) + } + + return out + } + var response []skills + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// LLMStepOpts contains options for LLM.Step +type LLMStepOpts struct { + // Cap the model's output tokens for this step. Defaults to the model's maximum. + MaxTokens int +} + +// Advance the conversation by a single step: send the queued prompt or tool results to the model, evaluate any tool calls it makes, and queue their results. Use loop to step until the model ends its turn. +func (r *LLM) Step(opts ...LLMStepOpts) *LLM { + q := r.query.Select("step") + for i := len(opts) - 1; i >= 0; i-- { + // `maxTokens` optional argument + if !querybuilder.IsZeroValue(opts[i].MaxTokens) { + q = q.Arg("maxTokens", opts[i].MaxTokens) + } + } + + return &LLM{ + query: q, + } +} + +// Force evaluation of the conversation's pending operations (prompts, steps, loops) in the engine. +func (r *LLM) Sync(ctx context.Context) (*LLM, error) { + q := r.query.Select("sync") + + var id ID + if err := q.Bind(&id).Execute(ctx); err != nil { + return nil, err + } + return &LLM{ + query: selectNode(q.Root(), id, "LLM"), + }, nil +} + +// The cumulative token usage, summed across every API call in the conversation. +func (r *LLM) TokenUsage() *LLMTokenUsage { + q := r.query.Select("tokenUsage") + + return &LLMTokenUsage{ + query: q, + } +} + +// Render documentation for the tools currently exposed to the model. +func (r *LLM) Tools(ctx context.Context) (string, error) { + if r.tools != nil { + return *r.tools, nil + } + q := r.query.Select("tools") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The message history rendered as a plain-text transcript, suitable for feeding back to an LLM (e.g. for summarization). +func (r *LLM) Transcript(ctx context.Context) (string, error) { + if r.transcript != nil { + return *r.transcript, nil + } + q := r.query.Select("transcript") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Add an external MCP server to the LLM +func (r *LLM) WithMCPServer(name string, service *Service) *LLM { + assertNotNil("service", service) + q := r.query.Select("withMCPServer") + q = q.Arg("name", name) + q = q.Arg("service", service) + + return &LLM{ + query: q, + } +} + +// LLMWithModelOpts contains options for LLM.WithModel +type LLMWithModelOpts struct { + // The provider serving the model, e.g. "openai". Overrides the provider otherwise inferred from the model name — useful when the name matches no known pattern (e.g. a fine-tune), or matches the wrong one. + Provider string +} + +// Change the model for the rest of the conversation. The message history is preserved; the new model takes effect on the next step. +func (r *LLM) WithModel(model string, opts ...LLMWithModelOpts) *LLM { + q := r.query.Select("withModel") + for i := len(opts) - 1; i >= 0; i-- { + // `provider` optional argument + if !querybuilder.IsZeroValue(opts[i].Provider) { + q = q.Arg("provider", opts[i].Provider) + } + } + q = q.Arg("model", model) + + return &LLM{ + query: q, + } +} + +// Queue a user prompt, to be sent to the model on the next step or loop. +func (r *LLM) WithPrompt(prompt string) *LLM { + q := r.query.Select("withPrompt") + q = q.Arg("prompt", prompt) + + return &LLM{ + query: q, + } +} + +// Queue a file's contents as a user prompt, like withPrompt. +func (r *LLM) WithPromptFile(file *File) *LLM { + assertNotNil("file", file) + q := r.query.Select("withPromptFile") + q = q.Arg("file", file) + + return &LLM{ + query: q, + } +} + +// Change the reasoning effort for the rest of the conversation, overriding any configured default. The message history is preserved; the new effort takes effect on the next step. +func (r *LLM) WithReasoningEffort(effort string) *LLM { + q := r.query.Select("withReasoningEffort") + q = q.Arg("effort", effort) + + return &LLM{ + query: q, + } +} + +// LLMWithResponseOpts contains options for LLM.WithResponse +type LLMWithResponseOpts struct { + // Uncached input tokens sent + InputTokens int + // Tokens received from the model, including text and tool calls + OutputTokens int + // Cached input tokens read + CachedTokenReads int + // Cached input tokens written + CachedTokenWrites int + // Total tokens consumed by this response + TotalTokens int +} + +// Append an assistant response to the message history without calling the model, e.g. to reconstruct a conversation from another source. +func (r *LLM) WithResponse(content []LLMContentBlockInput, opts ...LLMWithResponseOpts) *LLM { + q := r.query.Select("withResponse") + for i := len(opts) - 1; i >= 0; i-- { + // `inputTokens` optional argument + if !querybuilder.IsZeroValue(opts[i].InputTokens) { + q = q.Arg("inputTokens", opts[i].InputTokens) + } + // `outputTokens` optional argument + if !querybuilder.IsZeroValue(opts[i].OutputTokens) { + q = q.Arg("outputTokens", opts[i].OutputTokens) + } + // `cachedTokenReads` optional argument + if !querybuilder.IsZeroValue(opts[i].CachedTokenReads) { + q = q.Arg("cachedTokenReads", opts[i].CachedTokenReads) + } + // `cachedTokenWrites` optional argument + if !querybuilder.IsZeroValue(opts[i].CachedTokenWrites) { + q = q.Arg("cachedTokenWrites", opts[i].CachedTokenWrites) + } + // `totalTokens` optional argument + if !querybuilder.IsZeroValue(opts[i].TotalTokens) { + q = q.Arg("totalTokens", opts[i].TotalTokens) + } + } + q = q.Arg("content", content) + + return &LLM{ + query: q, + } +} + +// Install skills from a directory, adding them to the skills the model discovers with ListSkills and reads with ReadSkill. Each skill is a directory containing a SKILL.md with name and description frontmatter, discovered anywhere in the tree. Installed skills take precedence over skills discovered in the workspace, but cannot shadow the engine's built-in skills. +func (r *LLM) WithSkills(directory *Directory) *LLM { + assertNotNil("directory", directory) + q := r.query.Select("withSkills") + q = q.Arg("directory", directory) + + return &LLM{ + query: q, + } +} + +// Add a system prompt, instructing the model across the whole conversation. +func (r *LLM) WithSystemPrompt(prompt string) *LLM { + q := r.query.Select("withSystemPrompt") + q = q.Arg("prompt", prompt) + + return &LLM{ + query: q, + } +} + +// Append the result of a tool call to the message history. +func (r *LLM) WithToolResult(callId string, content string, errored bool) *LLM { + q := r.query.Select("withToolResult") + q = q.Arg("callId", callId) + q = q.Arg("content", content) + q = q.Arg("errored", errored) + + return &LLM{ + query: q, + } +} + +// LLMWithToolsOpts contains options for LLM.WithTools +type LLMWithToolsOpts struct { + // Method names to exclude from the toolset (e.g. constructors, entrypoints). + Except []string +} + +// Expose an object's methods as tools. Every eligible method of the bound object becomes a tool; a tool that returns this object's own type replaces it as the new state. Repeatable to bind several objects. +func (r *LLM) WithTools(object Node, opts ...LLMWithToolsOpts) *LLM { + q := r.query.Select("withTools") + for i := len(opts) - 1; i >= 0; i-- { + // `except` optional argument + if !querybuilder.IsZeroValue(opts[i].Except) { + q = q.Arg("except", opts[i].Except) + } + } + q = q.Arg("object", object) + + return &LLM{ + query: q, + } +} + +// Bind the LLM to a workspace, exposing its modules as tools exactly as the Dagger CLI would serve them for that workspace. +func (r *LLM) WithWorkspace(workspace *Workspace) *LLM { + assertNotNil("workspace", workspace) + q := r.query.Select("withWorkspace") + q = q.Arg("workspace", workspace) + + return &LLM{ + query: q, + } +} + +// Disable the default system prompt +func (r *LLM) WithoutDefaultSystemPrompt() *LLM { + q := r.query.Select("withoutDefaultSystemPrompt") + + return &LLM{ + query: q, + } +} + +// Clear the message history, keeping only the system prompts. +func (r *LLM) WithoutMessageHistory() *LLM { + q := r.query.Select("withoutMessageHistory") + + return &LLM{ + query: q, + } +} + +// Clear the user-added system prompts, keeping only the default system prompt. +func (r *LLM) WithoutSystemPrompts() *LLM { + q := r.query.Select("withoutSystemPrompts") + + return &LLM{ + query: q, + } +} + +// Return the workspace the LLM is bound to. +func (r *LLM) Workspace() *Workspace { + q := r.query.Select("workspace") + + return &Workspace{ + query: q, + } +} + +// AsNode returns this LLM as a Node. +// This is a local type conversion — no GraphQL call. +func (r *LLM) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// AsSyncer returns this LLM as a Syncer. +// This is a local type conversion — no GraphQL call. +func (r *LLM) AsSyncer() Syncer { + return &SyncerClient{ + query: r.query, + } +} + +// A single piece of content within an LLM message. +type LLMContentBlock struct { + query *querybuilder.Selection + + arguments *JSON + callId *string + errored *bool + id *ID + kind *LLMContentBlockKind + signature *string + text *string + toolName *string +} + +func (r *LLMContentBlock) WithGraphQLQuery(q *querybuilder.Selection) *LLMContentBlock { + return &LLMContentBlock{ + query: q, + } +} + +// The arguments passed to the tool, JSON-encoded (for TOOL_CALL kind). +func (r *LLMContentBlock) Arguments(ctx context.Context) (JSON, error) { + if r.arguments != nil { + return *r.arguments, nil + } + q := r.query.Select("arguments") + + var response JSON + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The unique ID of a tool call (for TOOL_CALL or TOOL_RESULT kinds). +func (r *LLMContentBlock) CallID(ctx context.Context) (string, error) { + if r.callId != nil { + return *r.callId, nil + } + q := r.query.Select("callId") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Whether the tool call resulted in an error (for TOOL_RESULT kind). +func (r *LLMContentBlock) Errored(ctx context.Context) (bool, error) { + if r.errored != nil { + return *r.errored, nil + } + q := r.query.Select("errored") + + var response bool + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this LLMContentBlock. +func (r *LLMContentBlock) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *LLMContentBlock) XXX_GraphQLType() string { + return "LLMContentBlock" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *LLMContentBlock) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *LLMContentBlock) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *LLMContentBlock) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *LLMContentBlock) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = LLMContentBlock{query: selectNode(dag.query, id, "LLMContentBlock")} + return nil +} + +// The kind of content block, which determines the other populated fields. +func (r *LLMContentBlock) Kind(ctx context.Context) (LLMContentBlockKind, error) { + if r.kind != nil { + return *r.kind, nil + } + q := r.query.Select("kind") + + var response LLMContentBlockKind + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Provider-specific opaque data (e.g. Anthropic thinking signature). Preserve it when reconstructing a conversation. +func (r *LLMContentBlock) Signature(ctx context.Context) (string, error) { + if r.signature != nil { + return *r.signature, nil + } + q := r.query.Select("signature") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Text content (for TEXT, THINKING, or TOOL_RESULT kinds). +func (r *LLMContentBlock) Text(ctx context.Context) (string, error) { + if r.text != nil { + return *r.text, nil + } + q := r.query.Select("text") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The name of the tool called (for TOOL_CALL kind). +func (r *LLMContentBlock) ToolName(ctx context.Context) (string, error) { + if r.toolName != nil { + return *r.toolName, nil + } + q := r.query.Select("toolName") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this LLMContentBlock as a Node. +// This is a local type conversion — no GraphQL call. +func (r *LLMContentBlock) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A single message in an LLM conversation. +type LLMMessage struct { + query *querybuilder.Selection + + id *ID + role *LLMMessageRole +} + +func (r *LLMMessage) WithGraphQLQuery(q *querybuilder.Selection) *LLMMessage { + return &LLMMessage{ + query: q, + } +} + +// The message's content blocks, in the order the model produced them. +func (r *LLMMessage) Content(ctx context.Context) ([]LLMContentBlock, error) { + q := r.query.Select("content") + + q = q.Select("id") + + type content struct { + Id ID + } + + convert := func(fields []content) []LLMContentBlock { + out := []LLMContentBlock{} + + for i := range fields { + val := LLMContentBlock{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "LLMContentBlock") + out = append(out, val) + } + + return out + } + var response []content + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// A unique identifier for this LLMMessage. +func (r *LLMMessage) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *LLMMessage) XXX_GraphQLType() string { + return "LLMMessage" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *LLMMessage) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *LLMMessage) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *LLMMessage) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *LLMMessage) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = LLMMessage{query: selectNode(dag.query, id, "LLMMessage")} + return nil +} + +// The role that produced this message. +func (r *LLMMessage) Role(ctx context.Context) (LLMMessageRole, error) { + if r.role != nil { + return *r.role, nil + } + q := r.query.Select("role") + + var response LLMMessageRole + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Token usage reported by the provider for the API call that produced this message; all zeros except on assistant responses. +func (r *LLMMessage) TokenUsage() *LLMTokenUsage { + q := r.query.Select("tokenUsage") + + return &LLMTokenUsage{ + query: q, + } +} + +// AsNode returns this LLMMessage as a Node. +// This is a local type conversion — no GraphQL call. +func (r *LLMMessage) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A skill available to a model: task-specific guidance discovered with ListSkills and read with ReadSkill. +type LLMSkill struct { + query *querybuilder.Selection + + description *string + id *ID + name *string +} + +func (r *LLMSkill) WithGraphQLQuery(q *querybuilder.Selection) *LLMSkill { + return &LLMSkill{ + query: q, + } +} + +// The one-line description from the SKILL.md frontmatter. +func (r *LLMSkill) Description(ctx context.Context) (string, error) { + if r.description != nil { + return *r.description, nil + } + q := r.query.Select("description") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this LLMSkill. +func (r *LLMSkill) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *LLMSkill) XXX_GraphQLType() string { + return "LLMSkill" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *LLMSkill) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *LLMSkill) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *LLMSkill) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *LLMSkill) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = LLMSkill{query: selectNode(dag.query, id, "LLMSkill")} + return nil +} + +// The skill name, as passed to ReadSkill. +func (r *LLMSkill) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this LLMSkill as a Node. +// This is a local type conversion — no GraphQL call. +func (r *LLMSkill) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A count of tokens consumed by LLM API calls. +type LLMTokenUsage struct { + query *querybuilder.Selection + + cachedTokenReads *int + cachedTokenWrites *int + id *ID + inputTokens *int + outputTokens *int + totalTokens *int +} + +func (r *LLMTokenUsage) WithGraphQLQuery(q *querybuilder.Selection) *LLMTokenUsage { + return &LLMTokenUsage{ + query: q, + } +} + +// Input tokens served from the provider's prompt cache. +func (r *LLMTokenUsage) CachedTokenReads(ctx context.Context) (int, error) { + if r.cachedTokenReads != nil { + return *r.cachedTokenReads, nil + } + q := r.query.Select("cachedTokenReads") + + var response int + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Input tokens written to the provider's prompt cache. +func (r *LLMTokenUsage) CachedTokenWrites(ctx context.Context) (int, error) { + if r.cachedTokenWrites != nil { + return *r.cachedTokenWrites, nil + } + q := r.query.Select("cachedTokenWrites") + + var response int + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this LLMTokenUsage. +func (r *LLMTokenUsage) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *LLMTokenUsage) XXX_GraphQLType() string { + return "LLMTokenUsage" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *LLMTokenUsage) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *LLMTokenUsage) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *LLMTokenUsage) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *LLMTokenUsage) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = LLMTokenUsage{query: selectNode(dag.query, id, "LLMTokenUsage")} + return nil +} + +// Uncached input tokens sent to the model. +func (r *LLMTokenUsage) InputTokens(ctx context.Context) (int, error) { + if r.inputTokens != nil { + return *r.inputTokens, nil + } + q := r.query.Select("inputTokens") + + var response int + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Tokens received from the model, including text and tool calls. +func (r *LLMTokenUsage) OutputTokens(ctx context.Context) (int, error) { + if r.outputTokens != nil { + return *r.outputTokens, nil + } + q := r.query.Select("outputTokens") + + var response int + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Total tokens consumed, as reported by the provider. +func (r *LLMTokenUsage) TotalTokens(ctx context.Context) (int, error) { + if r.totalTokens != nil { + return *r.totalTokens, nil + } + q := r.query.Select("totalTokens") + + var response int + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this LLMTokenUsage as a Node. +// This is a local type conversion — no GraphQL call. +func (r *LLMTokenUsage) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A simple key value object that represents a label. +type Label struct { + query *querybuilder.Selection + + id *ID + name *string + value *string +} + +func (r *Label) WithGraphQLQuery(q *querybuilder.Selection) *Label { + return &Label{ + query: q, + } +} + +// A unique identifier for this Label. +func (r *Label) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Label) XXX_GraphQLType() string { + return "Label" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Label) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Label) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Label) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Label) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = Label{query: selectNode(dag.query, id, "Label")} + return nil +} + +// The label name. +func (r *Label) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The label value. +func (r *Label) Value(ctx context.Context) (string, error) { + if r.value != nil { + return *r.value, nil + } + q := r.query.Select("value") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this Label as a Node. +// This is a local type conversion — no GraphQL call. +func (r *Label) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A definition of a list type in a Module. +type ListTypeDef struct { + query *querybuilder.Selection + + id *ID +} + +func (r *ListTypeDef) WithGraphQLQuery(q *querybuilder.Selection) *ListTypeDef { + return &ListTypeDef{ + query: q, + } +} + +// The type of the elements in the list. +func (r *ListTypeDef) ElementTypeDef() *TypeDef { + q := r.query.Select("elementTypeDef") + + return &TypeDef{ + query: q, + } +} + +// A unique identifier for this ListTypeDef. +func (r *ListTypeDef) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *ListTypeDef) XXX_GraphQLType() string { + return "ListTypeDef" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *ListTypeDef) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *ListTypeDef) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *ListTypeDef) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *ListTypeDef) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = ListTypeDef{query: selectNode(dag.query, id, "ListTypeDef")} + return nil +} + +// AsNode returns this ListTypeDef as a Node. +// This is a local type conversion — no GraphQL call. +func (r *ListTypeDef) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A Dagger module. +type Module struct { + query *querybuilder.Selection + + description *string + id *ID + name *string + serve *Void + sync *ID +} +type WithModuleFunc func(r *Module) *Module + +// With calls the provided function with current Module. +// +// This is useful for reusability and readability by not breaking the calling chain. +func (r *Module) With(f WithModuleFunc) *Module { + return f(r) +} + +func (r *Module) WithGraphQLQuery(q *querybuilder.Selection) *Module { + return &Module{ + query: q, + } +} + +// Return the check defined by the module with the given name. Must match to exactly one check. +// +// Experimental: This API is highly experimental and may be removed or replaced entirely. +func (r *Module) Check(name string) *Check { + q := r.query.Select("check") + q = q.Arg("name", name) + + return &Check{ + query: q, + } +} + +// ModuleChecksOpts contains options for Module.Checks +type ModuleChecksOpts struct { + // Only include checks matching the specified patterns + Include []string + // When true, only return annotated check functions; exclude generate-as-checks + NoGenerate bool +} + +// Return all checks defined by the module +// +// Experimental: This API is highly experimental and may be removed or replaced entirely. +func (r *Module) Checks(opts ...ModuleChecksOpts) *CheckGroup { + q := r.query.Select("checks") + for i := len(opts) - 1; i >= 0; i-- { + // `include` optional argument + if !querybuilder.IsZeroValue(opts[i].Include) { + q = q.Arg("include", opts[i].Include) + } + // `noGenerate` optional argument + if !querybuilder.IsZeroValue(opts[i].NoGenerate) { + q = q.Arg("noGenerate", opts[i].NoGenerate) + } + } + + return &CheckGroup{ + query: q, + } +} + +// The dependencies of the module. +func (r *Module) Dependencies(ctx context.Context) ([]Module, error) { + q := r.query.Select("dependencies") + + q = q.Select("id") + + type dependencies struct { + Id ID + } + + convert := func(fields []dependencies) []Module { + out := []Module{} + + for i := range fields { + val := Module{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "Module") + out = append(out, val) + } + + return out + } + var response []dependencies + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// The doc string of the module, if any +func (r *Module) Description(ctx context.Context) (string, error) { + if r.description != nil { + return *r.description, nil + } + q := r.query.Select("description") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Enumerations served by this module. +func (r *Module) Enums(ctx context.Context) ([]TypeDef, error) { + q := r.query.Select("enums") + + q = q.Select("id") + + type enums struct { + Id ID + } + + convert := func(fields []enums) []TypeDef { + out := []TypeDef{} + + for i := range fields { + val := TypeDef{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "TypeDef") + out = append(out, val) + } + + return out + } + var response []enums + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// The generated files and directories made on top of the module source's context directory. +func (r *Module) GeneratedContextDirectory() *Directory { + q := r.query.Select("generatedContextDirectory") + + return &Directory{ + query: q, + } +} + +// Return the generator defined by the module with the given name. Must match to exactly one generator. +// +// Experimental: This API is highly experimental and may be removed or replaced entirely. +func (r *Module) Generator(name string) *Generator { + q := r.query.Select("generator") + q = q.Arg("name", name) + + return &Generator{ + query: q, + } +} + +// ModuleGeneratorsOpts contains options for Module.Generators +type ModuleGeneratorsOpts struct { + // Only include generators matching the specified patterns + Include []string +} + +// Return all generators defined by the module +// +// Experimental: This API is highly experimental and may be removed or replaced entirely. +func (r *Module) Generators(opts ...ModuleGeneratorsOpts) *GeneratorGroup { + q := r.query.Select("generators") + for i := len(opts) - 1; i >= 0; i-- { + // `include` optional argument + if !querybuilder.IsZeroValue(opts[i].Include) { + q = q.Arg("include", opts[i].Include) + } + } + + return &GeneratorGroup{ + query: q, + } +} + +// A unique identifier for this Module. +func (r *Module) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Module) XXX_GraphQLType() string { + return "Module" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Module) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Module) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Module) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Module) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = Module{query: selectNode(dag.query, id, "Module")} + return nil +} + +// Interfaces served by this module. +func (r *Module) Interfaces(ctx context.Context) ([]TypeDef, error) { + q := r.query.Select("interfaces") + + q = q.Select("id") + + type interfaces struct { + Id ID + } + + convert := func(fields []interfaces) []TypeDef { + out := []TypeDef{} + + for i := range fields { + val := TypeDef{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "TypeDef") + out = append(out, val) + } + + return out + } + var response []interfaces + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// The introspection schema JSON file for this module. +// +// This file represents the schema visible to the module's source code, including all core types and those from the dependencies. +// +// Note: this is in the context of a module, so some core types may be hidden. +func (r *Module) IntrospectionSchemaJSON() *File { + q := r.query.Select("introspectionSchemaJSON") + + return &File{ + query: q, + } +} + +// The name of the module +func (r *Module) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Objects served by this module. +func (r *Module) Objects(ctx context.Context) ([]TypeDef, error) { + q := r.query.Select("objects") + + q = q.Select("id") + + type objects struct { + Id ID + } + + convert := func(fields []objects) []TypeDef { + out := []TypeDef{} + + for i := range fields { + val := TypeDef{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "TypeDef") + out = append(out, val) + } + + return out + } + var response []objects + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// The container that runs the module's entrypoint. It will fail to execute if the module doesn't compile. +func (r *Module) Runtime(ctx context.Context) (*Container, error) { + q := r.query.Select("runtime") + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &Container{ + query: selectNode(q.Root(), *objectID, "Container"), + }, nil +} + +// The SDK config used by this module. +func (r *Module) SDK(ctx context.Context) (*SDKConfig, error) { + q := r.query.Select("sdk") + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &SDKConfig{ + query: selectNode(q.Root(), *objectID, "SDKConfig"), + }, nil +} + +// ModuleServeOpts contains options for Module.Serve +type ModuleServeOpts struct { + // Expose the dependencies of this module to the client + IncludeDependencies bool + // Install the module as the entrypoint, promoting its main-object methods onto the Query root + Entrypoint bool +} + +// 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. +func (r *Module) Serve(ctx context.Context, opts ...ModuleServeOpts) error { + if r.serve != nil { + return nil + } + q := r.query.Select("serve") + for i := len(opts) - 1; i >= 0; i-- { + // `includeDependencies` optional argument + if !querybuilder.IsZeroValue(opts[i].IncludeDependencies) { + q = q.Arg("includeDependencies", opts[i].IncludeDependencies) + } + // `entrypoint` optional argument + if !querybuilder.IsZeroValue(opts[i].Entrypoint) { + q = q.Arg("entrypoint", opts[i].Entrypoint) + } + } + + return q.Execute(ctx) +} + +// ModuleServicesOpts contains options for Module.Services +type ModuleServicesOpts struct { + // Only include services matching the specified patterns + Include []string +} + +// Return all services defined by the module +// +// Experimental: This API is highly experimental and may be removed or replaced entirely. +func (r *Module) Services(opts ...ModuleServicesOpts) *UpGroup { + q := r.query.Select("services") + for i := len(opts) - 1; i >= 0; i-- { + // `include` optional argument + if !querybuilder.IsZeroValue(opts[i].Include) { + q = q.Arg("include", opts[i].Include) + } + } + + return &UpGroup{ + query: q, + } +} + +// The source for the module. +func (r *Module) Source(ctx context.Context) (*ModuleSource, error) { + q := r.query.Select("source") + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &ModuleSource{ + query: selectNode(q.Root(), *objectID, "ModuleSource"), + }, nil +} + +// Forces evaluation of the module, including any loading into the engine and associated validation. +func (r *Module) Sync(ctx context.Context) (*Module, error) { + q := r.query.Select("sync") + + var id ID + if err := q.Bind(&id).Execute(ctx); err != nil { + return nil, err + } + return &Module{ + query: selectNode(q.Root(), id, "Module"), + }, nil +} + +// User-defined default values, loaded from local .env files. +func (r *Module) UserDefaults() *EnvFile { + q := r.query.Select("userDefaults") + + return &EnvFile{ + query: q, + } +} + +// Retrieves the module with the given description +func (r *Module) WithDescription(description string) *Module { + q := r.query.Select("withDescription") + q = q.Arg("description", description) + + return &Module{ + query: q, + } +} + +// This module plus the given Enum type and associated values +func (r *Module) WithEnum(enum *TypeDef) *Module { + assertNotNil("enum", enum) + q := r.query.Select("withEnum") + q = q.Arg("enum", enum) + + return &Module{ + query: q, + } +} + +// This module plus the given Interface type and associated functions +func (r *Module) WithInterface(iface *TypeDef) *Module { + assertNotNil("iface", iface) + q := r.query.Select("withInterface") + q = q.Arg("iface", iface) + + return &Module{ + query: q, + } +} + +// This module plus the given Object type and associated functions. +func (r *Module) WithObject(object *TypeDef) *Module { + assertNotNil("object", object) + q := r.query.Select("withObject") + q = q.Arg("object", object) + + return &Module{ + query: q, + } +} + +// AsNode returns this Module as a Node. +// This is a local type conversion — no GraphQL call. +func (r *Module) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// AsSyncer returns this Module as a Syncer. +// This is a local type conversion — no GraphQL call. +func (r *Module) AsSyncer() Syncer { + return &SyncerClient{ + query: r.query, + } +} + +// The client generated for the module. +type ModuleConfigClient struct { + query *querybuilder.Selection + + directory *string + generator *string + id *ID +} + +func (r *ModuleConfigClient) WithGraphQLQuery(q *querybuilder.Selection) *ModuleConfigClient { + return &ModuleConfigClient{ + query: q, + } +} + +// The directory the client is generated in. +func (r *ModuleConfigClient) Directory(ctx context.Context) (string, error) { + if r.directory != nil { + return *r.directory, nil + } + q := r.query.Select("directory") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The generator to use +func (r *ModuleConfigClient) Generator(ctx context.Context) (string, error) { + if r.generator != nil { + return *r.generator, nil + } + q := r.query.Select("generator") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this ModuleConfigClient. +func (r *ModuleConfigClient) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *ModuleConfigClient) XXX_GraphQLType() string { + return "ModuleConfigClient" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *ModuleConfigClient) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *ModuleConfigClient) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *ModuleConfigClient) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *ModuleConfigClient) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = ModuleConfigClient{query: selectNode(dag.query, id, "ModuleConfigClient")} + return nil +} + +// AsNode returns this ModuleConfigClient as a Node. +// This is a local type conversion — no GraphQL call. +func (r *ModuleConfigClient) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// The source needed to load and run a module, along with any metadata about the source such as versions/urls/etc. +type ModuleSource struct { + query *querybuilder.Selection + + asString *string + cloneRef *string + commit *string + configExists *bool + digest *string + engineVersion *string + htmlRepoURL *string + htmlURL *string + id *ID + kind *ModuleSourceKind + localContextDirectoryPath *string + moduleName *string + moduleOriginalName *string + originalSubpath *string + pin *string + repoRootPath *string + sourceRootSubpath *string + sourceSubpath *string + sync *ID + version *string +} +type WithModuleSourceFunc func(r *ModuleSource) *ModuleSource + +// With calls the provided function with current ModuleSource. +// +// This is useful for reusability and readability by not breaking the calling chain. +func (r *ModuleSource) With(f WithModuleSourceFunc) *ModuleSource { + return f(r) +} + +func (r *ModuleSource) WithGraphQLQuery(q *querybuilder.Selection) *ModuleSource { + return &ModuleSource{ + query: q, + } +} + +// Load the source as a module. If this is a local source, the parent directory must have been provided during module source creation +func (r *ModuleSource) AsModule() *Module { + q := r.query.Select("asModule") + + return &Module{ + query: q, + } +} + +// A human readable ref string representation of this module source. +func (r *ModuleSource) AsString(ctx context.Context) (string, error) { + if r.asString != nil { + return *r.asString, nil + } + q := r.query.Select("asString") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The blueprint referenced by the module source. +// +// Deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in dagger.toml instead. +func (r *ModuleSource) Blueprint() *ModuleSource { + q := r.query.Select("blueprint") + + return &ModuleSource{ + query: q, + } +} + +// The client-facing introspection schema JSON file for this module source. +// +// This is the schema consumed by client codegen: unlike introspectionSchemaJSON (the module-facing schema), it hides no core types and installs this module (reached via dag.) so a generated client can bind it. The module's dependencies are excluded: a client is generated for a single module plus core, not its dependency graph. +func (r *ModuleSource) ClientSchemaIntrospectionJSON() *File { + q := r.query.Select("clientSchemaIntrospectionJSON") + + return &File{ + query: q, + } +} + +// The ref to clone the root of the git repo from. Only valid for git sources. +func (r *ModuleSource) CloneRef(ctx context.Context) (string, error) { + if r.cloneRef != nil { + return *r.cloneRef, nil + } + q := r.query.Select("cloneRef") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The resolved commit of the git repo this source points to. +func (r *ModuleSource) Commit(ctx context.Context) (string, error) { + if r.commit != nil { + return *r.commit, nil + } + q := r.query.Select("commit") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The clients generated for the module. +func (r *ModuleSource) ConfigClients(ctx context.Context) ([]ModuleConfigClient, error) { + q := r.query.Select("configClients") + + q = q.Select("id") + + type configClients struct { + Id ID + } + + convert := func(fields []configClients) []ModuleConfigClient { + out := []ModuleConfigClient{} + + for i := range fields { + val := ModuleConfigClient{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "ModuleConfigClient") + out = append(out, val) + } + + return out + } + var response []configClients + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// Whether an existing module config file was found. +func (r *ModuleSource) ConfigExists(ctx context.Context) (bool, error) { + if r.configExists != nil { + return *r.configExists, nil + } + q := r.query.Select("configExists") + + var response bool + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The full directory loaded for the module source, including the source code as a subdirectory. +func (r *ModuleSource) ContextDirectory() *Directory { + q := r.query.Select("contextDirectory") + + return &Directory{ + query: q, + } +} + +// The dependencies of the module source. +func (r *ModuleSource) Dependencies(ctx context.Context) ([]ModuleSource, error) { + q := r.query.Select("dependencies") + + q = q.Select("id") + + type dependencies struct { + Id ID + } + + convert := func(fields []dependencies) []ModuleSource { + out := []ModuleSource{} + + for i := range fields { + val := ModuleSource{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "ModuleSource") + out = append(out, val) + } + + return out + } + var response []dependencies + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// 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. +func (r *ModuleSource) Digest(ctx context.Context) (string, error) { + if r.digest != nil { + return *r.digest, nil + } + q := r.query.Select("digest") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The directory containing the module configuration and source code (source code may be in a subdir). +func (r *ModuleSource) Directory(path string) *Directory { + q := r.query.Select("directory") + q = q.Arg("path", path) + + return &Directory{ + query: q, + } +} + +// The engine version of the module. +func (r *ModuleSource) EngineVersion(ctx context.Context) (string, error) { + if r.engineVersion != nil { + return *r.engineVersion, nil + } + q := r.query.Select("engineVersion") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Return the supplied workspace with this module's generated context applied. +// +// The workspace change baseline is preserved, so a later Workspace.changes call includes this generation together with any other edits made by the caller. +func (r *ModuleSource) Generate(workspace *Workspace) *Workspace { + assertNotNil("workspace", workspace) + q := r.query.Select("generate") + q = q.Arg("workspace", workspace) + + return &Workspace{ + query: q, + } +} + +// Generate this module's transitive local dependency closure and return the staged changes as a single changeset against the unstaged workspace root. +// +// Each local dependency is generated by its own SDK against a workspace scoped to it, carrying the dependency's own already-generated dependencies. Remote (git) dependencies are assumed committed and skipped. Overlay the result onto the workspace before generating this module; it is not this module's own generated code. +func (r *ModuleSource) GenerateLocalDependencies(workspace *Workspace) *Changeset { + assertNotNil("workspace", workspace) + q := r.query.Select("generateLocalDependencies") + q = q.Arg("workspace", workspace) + + return &Changeset{ + query: q, + } +} + +// The generated files and directories made on top of the module source's context directory, returned as a Changeset. +func (r *ModuleSource) GeneratedContextChangeset() *Changeset { + q := r.query.Select("generatedContextChangeset") + + return &Changeset{ + query: q, + } +} + +// The generated files and directories made on top of the module source's context directory. +func (r *ModuleSource) GeneratedContextDirectory() *Directory { + q := r.query.Select("generatedContextDirectory") + + return &Directory{ + query: q, + } +} + +// The URL to access the web view of the repository (e.g., GitHub, GitLab, Bitbucket). +func (r *ModuleSource) HTMLRepoURL(ctx context.Context) (string, error) { + if r.htmlRepoURL != nil { + return *r.htmlRepoURL, nil + } + q := r.query.Select("htmlRepoURL") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The URL to the source's git repo in a web browser. Only valid for git sources. +func (r *ModuleSource) HTMLURL(ctx context.Context) (string, error) { + if r.htmlURL != nil { + return *r.htmlURL, nil + } + q := r.query.Select("htmlURL") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this ModuleSource. +func (r *ModuleSource) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *ModuleSource) XXX_GraphQLType() string { + return "ModuleSource" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *ModuleSource) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *ModuleSource) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *ModuleSource) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *ModuleSource) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = ModuleSource{query: selectNode(dag.query, id, "ModuleSource")} + return nil +} + +// The introspection schema JSON file for this module source. +// +// This file represents the schema visible to the module's source code, including all core types and those from the dependencies. +// +// Note: this is in the context of a module, so some core types may be hidden. +func (r *ModuleSource) IntrospectionSchemaJSON() *File { + q := r.query.Select("introspectionSchemaJSON") + + return &File{ + query: q, + } +} + +// The kind of module source (currently local, git or dir). +func (r *ModuleSource) Kind(ctx context.Context) (ModuleSourceKind, error) { + if r.kind != nil { + return *r.kind, nil + } + q := r.query.Select("kind") + + var response ModuleSourceKind + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// 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. +func (r *ModuleSource) LocalContextDirectoryPath(ctx context.Context) (string, error) { + if r.localContextDirectoryPath != nil { + return *r.localContextDirectoryPath, nil + } + q := r.query.Select("localContextDirectoryPath") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The name of the module, including any setting via the withName API. +func (r *ModuleSource) ModuleName(ctx context.Context) (string, error) { + if r.moduleName != nil { + return *r.moduleName, nil + } + q := r.query.Select("moduleName") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The original name of the module as read from the module config file (or set for the first time with the withName API). +func (r *ModuleSource) ModuleOriginalName(ctx context.Context) (string, error) { + if r.moduleOriginalName != nil { + return *r.moduleOriginalName, nil + } + q := r.query.Select("moduleOriginalName") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The original subpath used when instantiating this module source, relative to the context directory. +func (r *ModuleSource) OriginalSubpath(ctx context.Context) (string, error) { + if r.originalSubpath != nil { + return *r.originalSubpath, nil + } + q := r.query.Select("originalSubpath") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The pinned version of this module source. +func (r *ModuleSource) Pin(ctx context.Context) (string, error) { + if r.pin != nil { + return *r.pin, nil + } + q := r.query.Select("pin") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The import path corresponding to the root of the git repo this source points to. Only valid for git sources. +func (r *ModuleSource) RepoRootPath(ctx context.Context) (string, error) { + if r.repoRootPath != nil { + return *r.repoRootPath, nil + } + q := r.query.Select("repoRootPath") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The SDK configuration of the module. +func (r *ModuleSource) SDK(ctx context.Context) (*SDKConfig, error) { + q := r.query.Select("sdk") + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &SDKConfig{ + query: selectNode(q.Root(), *objectID, "SDKConfig"), + }, nil +} + +// The path, relative to the context directory, that contains the module config. +func (r *ModuleSource) SourceRootSubpath(ctx context.Context) (string, error) { + if r.sourceRootSubpath != nil { + return *r.sourceRootSubpath, nil + } + q := r.query.Select("sourceRootSubpath") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The path to the directory containing the module's source code, relative to the context directory. +func (r *ModuleSource) SourceSubpath(ctx context.Context) (string, error) { + if r.sourceSubpath != nil { + return *r.sourceSubpath, nil + } + q := r.query.Select("sourceSubpath") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Forces evaluation of the module source, including any loading into the engine and associated validation. +func (r *ModuleSource) Sync(ctx context.Context) (*ModuleSource, error) { + q := r.query.Select("sync") + + var id ID + if err := q.Bind(&id).Execute(ctx); err != nil { + return nil, err + } + return &ModuleSource{ + query: selectNode(q.Root(), id, "ModuleSource"), + }, nil +} + +// The toolchains referenced by the module source. +// +// Deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in dagger.toml instead. +func (r *ModuleSource) Toolchains(ctx context.Context) ([]ModuleSource, error) { + q := r.query.Select("toolchains") + + q = q.Select("id") + + type toolchains struct { + Id ID + } + + convert := func(fields []toolchains) []ModuleSource { + out := []ModuleSource{} + + for i := range fields { + val := ModuleSource{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "ModuleSource") + out = append(out, val) + } + + return out + } + var response []toolchains + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// The module's dagger.json with any in-memory edits from with* APIs applied, as a diff relative to the source's context directory. +// +// Unlike generatedContextDirectory, this does not run codegen and does not validate the engine version against the running engine, so it can be used to declare an engine requirement newer than the running engine. Loading or serving such a module still fails at moduleSource.asModule. +func (r *ModuleSource) UpdatedConfigDirectory() *Directory { + q := r.query.Select("updatedConfigDirectory") + + return &Directory{ + query: q, + } +} + +// User-defined defaults read from local .env files +func (r *ModuleSource) UserDefaults() *EnvFile { + q := r.query.Select("userDefaults") + + return &EnvFile{ + query: q, + } +} + +// The specified version of the git repo this source points to. +func (r *ModuleSource) Version(ctx context.Context) (string, error) { + if r.version != nil { + return *r.version, nil + } + q := r.query.Select("version") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Set a blueprint for the module source. +// +// Deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in `dagger.toml` instead. +func (r *ModuleSource) WithBlueprint(blueprint *ModuleSource) *ModuleSource { + assertNotNil("blueprint", blueprint) + q := r.query.Select("withBlueprint") + q = q.Arg("blueprint", blueprint) + + return &ModuleSource{ + query: q, + } +} + +// Update the module source with a new client to generate. +func (r *ModuleSource) WithClient(generator string, outputDir string) *ModuleSource { + q := r.query.Select("withClient") + q = q.Arg("generator", generator) + q = q.Arg("outputDir", outputDir) + + return &ModuleSource{ + query: q, + } +} + +// Append the provided dependencies to the module source's dependency list. +func (r *ModuleSource) WithDependencies(dependencies []*ModuleSource) *ModuleSource { + q := r.query.Select("withDependencies") + q = q.Arg("dependencies", dependencies) + + return &ModuleSource{ + query: q, + } +} + +// Upgrade the engine version of the module to the given value. +func (r *ModuleSource) WithEngineVersion(version string) *ModuleSource { + q := r.query.Select("withEngineVersion") + q = q.Arg("version", version) + + return &ModuleSource{ + query: q, + } +} + +// Enable the experimental features for the module source. +func (r *ModuleSource) WithExperimentalFeatures(features []ModuleSourceExperimentalFeature) *ModuleSource { + q := r.query.Select("withExperimentalFeatures") + q = q.Arg("features", features) + + return &ModuleSource{ + query: q, + } +} + +// Update the module source with additional include patterns for files+directories from its context that are required for building it +func (r *ModuleSource) WithIncludes(patterns []string) *ModuleSource { + q := r.query.Select("withIncludes") + q = q.Arg("patterns", patterns) + + return &ModuleSource{ + query: q, + } +} + +// Update the module source with a new name. +func (r *ModuleSource) WithName(name string) *ModuleSource { + q := r.query.Select("withName") + q = q.Arg("name", name) + + return &ModuleSource{ + query: q, + } +} + +// Update the module source with a new SDK. +func (r *ModuleSource) WithSDK(source string) *ModuleSource { + q := r.query.Select("withSDK") + q = q.Arg("source", source) + + return &ModuleSource{ + query: q, + } +} + +// Update the module source with a new source subpath. +func (r *ModuleSource) WithSourceSubpath(path string) *ModuleSource { + q := r.query.Select("withSourceSubpath") + q = q.Arg("path", path) + + return &ModuleSource{ + query: q, + } +} + +// Add toolchains to the module source. +// +// Deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in `dagger.toml` instead. +func (r *ModuleSource) WithToolchains(toolchains []*ModuleSource) *ModuleSource { + q := r.query.Select("withToolchains") + q = q.Arg("toolchains", toolchains) + + return &ModuleSource{ + query: q, + } +} + +// Update the blueprint module to the latest version. +// +// Deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in `dagger.toml` instead. +func (r *ModuleSource) WithUpdateBlueprint() *ModuleSource { + q := r.query.Select("withUpdateBlueprint") + + return &ModuleSource{ + query: q, + } +} + +// Update one or more module dependencies. +func (r *ModuleSource) WithUpdateDependencies(dependencies []string) *ModuleSource { + q := r.query.Select("withUpdateDependencies") + q = q.Arg("dependencies", dependencies) + + return &ModuleSource{ + query: q, + } +} + +// Update one or more toolchains. +// +// Deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in `dagger.toml` instead. +func (r *ModuleSource) WithUpdateToolchains(toolchains []string) *ModuleSource { + q := r.query.Select("withUpdateToolchains") + q = q.Arg("toolchains", toolchains) + + return &ModuleSource{ + query: q, + } +} + +// Update one or more clients. +func (r *ModuleSource) WithUpdatedClients(clients []string) *ModuleSource { + q := r.query.Select("withUpdatedClients") + q = q.Arg("clients", clients) + + return &ModuleSource{ + query: q, + } +} + +// Remove the current blueprint from the module source. +// +// Deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in `dagger.toml` instead. +func (r *ModuleSource) WithoutBlueprint() *ModuleSource { + q := r.query.Select("withoutBlueprint") + + return &ModuleSource{ + query: q, + } +} + +// Remove a client from the module source. +func (r *ModuleSource) WithoutClient(path string) *ModuleSource { + q := r.query.Select("withoutClient") + q = q.Arg("path", path) + + return &ModuleSource{ + query: q, + } +} + +// Remove the provided dependencies from the module source's dependency list. +func (r *ModuleSource) WithoutDependencies(dependencies []string) *ModuleSource { + q := r.query.Select("withoutDependencies") + q = q.Arg("dependencies", dependencies) + + return &ModuleSource{ + query: q, + } +} + +// Disable experimental features for the module source. +func (r *ModuleSource) WithoutExperimentalFeatures(features []ModuleSourceExperimentalFeature) *ModuleSource { + q := r.query.Select("withoutExperimentalFeatures") + q = q.Arg("features", features) + + return &ModuleSource{ + query: q, + } +} + +// Remove the provided toolchains from the module source. +// +// Deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in `dagger.toml` instead. +func (r *ModuleSource) WithoutToolchains(toolchains []string) *ModuleSource { + q := r.query.Select("withoutToolchains") + q = q.Arg("toolchains", toolchains) + + return &ModuleSource{ + query: q, + } +} + +// AsNode returns this ModuleSource as a Node. +// This is a local type conversion — no GraphQL call. +func (r *ModuleSource) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// AsSyncer returns this ModuleSource as a Syncer. +// This is a local type conversion — no GraphQL call. +func (r *ModuleSource) AsSyncer() Syncer { + return &SyncerClient{ + query: r.query, + } +} + +// A definition of a custom object defined in a Module. +type ObjectTypeDef struct { + query *querybuilder.Selection + + deprecated *string + description *string + id *ID + name *string + sourceModuleName *string +} + +func (r *ObjectTypeDef) WithGraphQLQuery(q *querybuilder.Selection) *ObjectTypeDef { + return &ObjectTypeDef{ + query: q, + } +} + +// The function used to construct new instances of this object, if any. +func (r *ObjectTypeDef) Constructor(ctx context.Context) (*Function, error) { + q := r.query.Select("constructor") + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &Function{ + query: selectNode(q.Root(), *objectID, "Function"), + }, nil +} + +// The reason this enum member is deprecated, if any. +func (r *ObjectTypeDef) Deprecated(ctx context.Context) (string, error) { + if r.deprecated != nil { + return *r.deprecated, nil + } + q := r.query.Select("deprecated") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The doc string for the object, if any. +func (r *ObjectTypeDef) Description(ctx context.Context) (string, error) { + if r.description != nil { + return *r.description, nil + } + q := r.query.Select("description") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Static fields defined on this object, if any. +func (r *ObjectTypeDef) Fields(ctx context.Context) ([]FieldTypeDef, error) { + q := r.query.Select("fields") + + q = q.Select("id") + + type fields struct { + Id ID + } + + convert := func(fields []fields) []FieldTypeDef { + out := []FieldTypeDef{} + + for i := range fields { + val := FieldTypeDef{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "FieldTypeDef") + out = append(out, val) + } + + return out + } + var response []fields + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// Functions defined on this object, if any. +func (r *ObjectTypeDef) Functions(ctx context.Context) ([]Function, error) { + q := r.query.Select("functions") + + q = q.Select("id") + + type functions struct { + Id ID + } + + convert := func(fields []functions) []Function { + out := []Function{} + + for i := range fields { + val := Function{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "Function") + out = append(out, val) + } + + return out + } + var response []functions + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// A unique identifier for this ObjectTypeDef. +func (r *ObjectTypeDef) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *ObjectTypeDef) XXX_GraphQLType() string { + return "ObjectTypeDef" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *ObjectTypeDef) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *ObjectTypeDef) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *ObjectTypeDef) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *ObjectTypeDef) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = ObjectTypeDef{query: selectNode(dag.query, id, "ObjectTypeDef")} + return nil +} + +// The name of the object. +func (r *ObjectTypeDef) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The location of this object declaration. +func (r *ObjectTypeDef) SourceMap(ctx context.Context) (*SourceMap, error) { + q := r.query.Select("sourceMap") + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &SourceMap{ + query: selectNode(q.Root(), *objectID, "SourceMap"), + }, nil +} + +// If this ObjectTypeDef is associated with a Module, the name of the module. Unset otherwise. +func (r *ObjectTypeDef) SourceModuleName(ctx context.Context) (string, error) { + if r.sourceModuleName != nil { + return *r.sourceModuleName, nil + } + q := r.query.Select("sourceModuleName") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this ObjectTypeDef as a Node. +// This is a local type conversion — no GraphQL call. +func (r *ObjectTypeDef) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A port exposed by a container. +type Port struct { + query *querybuilder.Selection + + description *string + experimentalSkipHealthcheck *bool + id *ID + port *int + protocol *NetworkProtocol +} + +func (r *Port) WithGraphQLQuery(q *querybuilder.Selection) *Port { + return &Port{ + query: q, + } +} + +// The port description. +func (r *Port) Description(ctx context.Context) (string, error) { + if r.description != nil { + return *r.description, nil + } + q := r.query.Select("description") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Skip the health check when run as a service. +func (r *Port) ExperimentalSkipHealthcheck(ctx context.Context) (bool, error) { + if r.experimentalSkipHealthcheck != nil { + return *r.experimentalSkipHealthcheck, nil + } + q := r.query.Select("experimentalSkipHealthcheck") + + var response bool + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this Port. +func (r *Port) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Port) XXX_GraphQLType() string { + return "Port" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Port) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Port) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Port) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Port) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = Port{query: selectNode(dag.query, id, "Port")} + return nil +} + +// The port number. +func (r *Port) Port(ctx context.Context) (int, error) { + if r.port != nil { + return *r.port, nil + } + q := r.query.Select("port") + + var response int + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The transport layer protocol. +func (r *Port) Protocol(ctx context.Context) (NetworkProtocol, error) { + if r.protocol != nil { + return *r.protocol, nil + } + q := r.query.Select("protocol") + + var response NetworkProtocol + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this Port as a Node. +// This is a local type conversion — no GraphQL call. +func (r *Port) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// The root of the DAG. +type Query struct { + query *querybuilder.Selection + + defaultPlatform *Platform + id *ID + version *string +} + +func (r *Query) WithGraphQLQuery(q *querybuilder.Selection) *Query { + return &Query{ + query: q, + } +} + +// initialize an address to load directories, containers, secrets or other object types. +func (r *Query) Address(value string) *Address { + q := r.query.Select("address") + q = q.Arg("value", value) + + return &Address{ + query: q, + } +} + +// CacheVolumeOpts contains options for Query.CacheVolume +type CacheVolumeOpts struct { + // Identifier of the directory to use as the cache volume's root. + Source *Directory + // Sharing mode of the cache volume. + // + // Default: SHARED + Sharing CacheSharingMode + // A user:group to set for the cache volume root. + // + // 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. + Owner string +} + +// Constructs a cache volume for a given cache key. +func (r *Query) CacheVolume(key string, opts ...CacheVolumeOpts) *CacheVolume { + q := r.query.Select("cacheVolume") + for i := len(opts) - 1; i >= 0; i-- { + // `source` optional argument + if !querybuilder.IsZeroValue(opts[i].Source) { + q = q.Arg("source", opts[i].Source) + } + // `sharing` optional argument + if !querybuilder.IsZeroValue(opts[i].Sharing) { + q = q.Arg("sharing", opts[i].Sharing) + } + // `owner` optional argument + if !querybuilder.IsZeroValue(opts[i].Owner) { + q = q.Arg("owner", opts[i].Owner) + } + } + q = q.Arg("key", key) + + return &CacheVolume{ + query: q, + } +} + +// Creates an empty changeset +func (r *Query) Changeset() *Changeset { + q := r.query.Select("changeset") + + return &Changeset{ + query: q, + } +} + +// Dagger Cloud configuration and state +func (r *Query) Cloud() *Cloud { + q := r.query.Select("cloud") + + return &Cloud{ + query: q, + } +} + +// ContainerOpts contains options for Query.Container +type ContainerOpts struct { + // Platform to initialize the container with. Defaults to the native platform of the current engine + Platform Platform +} + +// Creates a scratch container, with no image or metadata. +// +// To pull an image, follow up with the "from" function. +func (r *Query) Container(opts ...ContainerOpts) *Container { + q := r.query.Select("container") + for i := len(opts) - 1; i >= 0; i-- { + // `platform` optional argument + if !querybuilder.IsZeroValue(opts[i].Platform) { + q = q.Arg("platform", opts[i].Platform) + } + } + + return &Container{ + query: q, + } +} + +// 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. +func (r *Query) CurrentFunctionCall() *FunctionCall { + q := r.query.Select("currentFunctionCall") + + return &FunctionCall{ + query: q, + } +} + +// The module currently being served in the session, if any. +func (r *Query) CurrentModule() *CurrentModule { + q := r.query.Select("currentModule") + + return &CurrentModule{ + query: q, + } +} + +// The object that received the current module function call, as a Node. Errors when there is no current call, or the call is top-level (e.g. a module constructor). +func (r *Query) CurrentNode() Node { + q := r.query.Select("currentNode") + return &NodeClient{ + query: q, + } +} + +// CurrentTypeDefsOpts contains options for Query.CurrentTypeDefs +type CurrentTypeDefsOpts struct { + // Return the full referenced typedef closure instead of only top-level served typedefs. + ReturnAllTypes bool + // Strip core API functions from the Query type, leaving only module-sourced functions (constructors, entrypoint proxies, etc.). + // + // Core types (Container, Directory, etc.) are kept so return types and method chaining still work. + HideCore bool +} + +// The TypeDef representations of the objects currently being served in the session. +func (r *Query) CurrentTypeDefs(ctx context.Context, opts ...CurrentTypeDefsOpts) ([]TypeDef, error) { + q := r.query.Select("currentTypeDefs") + for i := len(opts) - 1; i >= 0; i-- { + // `returnAllTypes` optional argument + if !querybuilder.IsZeroValue(opts[i].ReturnAllTypes) { + q = q.Arg("returnAllTypes", opts[i].ReturnAllTypes) + } + // `hideCore` optional argument + if !querybuilder.IsZeroValue(opts[i].HideCore) { + q = q.Arg("hideCore", opts[i].HideCore) + } + } + + q = q.Select("id") + + type currentTypeDefs struct { + Id ID + } + + convert := func(fields []currentTypeDefs) []TypeDef { + out := []TypeDef{} + + for i := range fields { + val := TypeDef{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "TypeDef") + out = append(out, val) + } + + return out + } + var response []currentTypeDefs + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// The default platform of the engine. +func (r *Query) DefaultPlatform(ctx context.Context) (Platform, error) { + q := r.query.Select("defaultPlatform") + + var response Platform + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Creates an empty directory. +func (r *Query) Directory() *Directory { + q := r.query.Select("directory") + + return &Directory{ + query: q, + } +} + +// EnvFileOpts contains options for Query.EnvFile +type EnvFileOpts struct { + // Replace "${VAR}" or "$VAR" with the value of other vars + // Deprecated: Variable expansion is now enabled by default + Expand bool +} + +// Initialize an environment file +func (r *Query) EnvFile(opts ...EnvFileOpts) *EnvFile { + q := r.query.Select("envFile") + for i := len(opts) - 1; i >= 0; i-- { + // `expand` optional argument + if !querybuilder.IsZeroValue(opts[i].Expand) { + q = q.Arg("expand", opts[i].Expand) + } + } + + return &EnvFile{ + query: q, + } +} + +// Create a new error. +func (r *Query) Error(message string) *Error { + q := r.query.Select("error") + q = q.Arg("message", message) + + return &Error{ + query: q, + } +} + +// FileOpts contains options for Query.File +type FileOpts struct { + // Permissions of the new file. Example: 0600 + // + // Default: 420 + Permissions int +} + +// Creates a file with the specified contents. +func (r *Query) File(name string, contents string, opts ...FileOpts) *File { + q := r.query.Select("file") + for i := len(opts) - 1; i >= 0; i-- { + // `permissions` optional argument + if !querybuilder.IsZeroValue(opts[i].Permissions) { + q = q.Arg("permissions", opts[i].Permissions) + } + } + q = q.Arg("name", name) + q = q.Arg("contents", contents) + + return &File{ + query: q, + } +} + +// Creates a function. +func (r *Query) Function(name string, returnType *TypeDef) *Function { + assertNotNil("returnType", returnType) + q := r.query.Select("function") + q = q.Arg("name", name) + q = q.Arg("returnType", returnType) + + return &Function{ + query: q, + } +} + +// Create a code generation result, given a directory containing the generated code. +func (r *Query) GeneratedCode(code *Directory) *GeneratedCode { + assertNotNil("code", code) + q := r.query.Select("generatedCode") + q = q.Arg("code", code) + + return &GeneratedCode{ + query: q, + } +} + +// GitOpts contains options for Query.Git +type GitOpts struct { + // DEPRECATED: Set to true to keep .git directory. + // + // Default: true + // Deprecated: Set to true to keep .git directory. + KeepGitDir bool + // Set SSH known hosts + SSHKnownHosts string + // Set SSH auth socket + SSHAuthSocket *Socket + // Username used to populate the password during basic HTTP Authorization + HTTPAuthUsername string + // Secret used to populate the password during basic HTTP Authorization + HTTPAuthToken *Secret + // Secret used to populate the Authorization HTTP header + HTTPAuthHeader *Secret + // A service which must be started before the repo is fetched. + ExperimentalServiceHost *Service +} + +// Queries a Git repository. +func (r *Query) Git(url string, opts ...GitOpts) *GitRepository { + q := r.query.Select("git") + for i := len(opts) - 1; i >= 0; i-- { + // `keepGitDir` optional argument + if !querybuilder.IsZeroValue(opts[i].KeepGitDir) { + q = q.Arg("keepGitDir", opts[i].KeepGitDir) + } + // `sshKnownHosts` optional argument + if !querybuilder.IsZeroValue(opts[i].SSHKnownHosts) { + q = q.Arg("sshKnownHosts", opts[i].SSHKnownHosts) + } + // `sshAuthSocket` optional argument + if !querybuilder.IsZeroValue(opts[i].SSHAuthSocket) { + q = q.Arg("sshAuthSocket", opts[i].SSHAuthSocket) + } + // `httpAuthUsername` optional argument + if !querybuilder.IsZeroValue(opts[i].HTTPAuthUsername) { + q = q.Arg("httpAuthUsername", opts[i].HTTPAuthUsername) + } + // `httpAuthToken` optional argument + if !querybuilder.IsZeroValue(opts[i].HTTPAuthToken) { + q = q.Arg("httpAuthToken", opts[i].HTTPAuthToken) + } + // `httpAuthHeader` optional argument + if !querybuilder.IsZeroValue(opts[i].HTTPAuthHeader) { + q = q.Arg("httpAuthHeader", opts[i].HTTPAuthHeader) + } + // `experimentalServiceHost` optional argument + if !querybuilder.IsZeroValue(opts[i].ExperimentalServiceHost) { + q = q.Arg("experimentalServiceHost", opts[i].ExperimentalServiceHost) + } + } + q = q.Arg("url", url) + + return &GitRepository{ + query: q, + } +} + +// HTTPOpts contains options for Query.HTTP +type HTTPOpts struct { + // File name to use for the file. Defaults to the last part of the URL. + Name string + // Permissions to set on the file. + Permissions int + // Expected digest of the downloaded content (e.g., "sha256:..."). + Checksum string + // Secret used to populate the Authorization HTTP header + AuthHeader *Secret + // A service which must be started before the URL is fetched. + ExperimentalServiceHost *Service +} + +// Returns a file containing an http remote url content. +func (r *Query) HTTP(url string, opts ...HTTPOpts) *File { + q := r.query.Select("http") + for i := len(opts) - 1; i >= 0; i-- { + // `name` optional argument + if !querybuilder.IsZeroValue(opts[i].Name) { + q = q.Arg("name", opts[i].Name) + } + // `permissions` optional argument + if !querybuilder.IsZeroValue(opts[i].Permissions) { + q = q.Arg("permissions", opts[i].Permissions) + } + // `checksum` optional argument + if !querybuilder.IsZeroValue(opts[i].Checksum) { + q = q.Arg("checksum", opts[i].Checksum) + } + // `authHeader` optional argument + if !querybuilder.IsZeroValue(opts[i].AuthHeader) { + q = q.Arg("authHeader", opts[i].AuthHeader) + } + // `experimentalServiceHost` optional argument + if !querybuilder.IsZeroValue(opts[i].ExperimentalServiceHost) { + q = q.Arg("experimentalServiceHost", opts[i].ExperimentalServiceHost) + } + } + q = q.Arg("url", url) + + return &File{ + query: q, + } +} + +// A unique identifier for this Query. +func (r *Query) ID(ctx context.Context) (ID, error) { + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Query) XXX_GraphQLType() string { + return "Query" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Query) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Query) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Query) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Query) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = Query{query: selectNode(dag.query, id, "Query")} + return nil +} + +// Initialize a JSON value +func (r *Query) JSON() *JSONValue { + q := r.query.Select("json") + + return &JSONValue{ + query: q, + } +} + +// LLMOpts contains options for Query.LLM +type LLMOpts struct { + // The model to converse with, e.g. "claude-sonnet-4-5" or "gpt-5.4". Defaults to the configured default model. + Model string + // The provider serving the model, e.g. "openai". Overrides the provider otherwise inferred from the model name — useful when the name matches no known pattern (e.g. a fine-tune), or matches the wrong one. + Provider string +} + +// Initialize a new LLM conversation. +// +// Experimental: LLM support is not yet stabilized +func (r *Query) LLM(opts ...LLMOpts) *LLM { + q := r.query.Select("llm") + for i := len(opts) - 1; i >= 0; i-- { + // `model` optional argument + if !querybuilder.IsZeroValue(opts[i].Model) { + q = q.Arg("model", opts[i].Model) + } + // `provider` optional argument + if !querybuilder.IsZeroValue(opts[i].Provider) { + q = q.Arg("provider", opts[i].Provider) + } + } + + return &LLM{ + query: q, + } +} + +// Create a new module. +func (r *Query) Module() *Module { + q := r.query.Select("module") + + return &Module{ + query: q, + } +} + +// ModuleSourceOpts contains options for Query.ModuleSource +type ModuleSourceOpts struct { + // The pinned version of the module source + RefPin string + // If true, do not attempt to find a module config file in a parent directory of the provided path. Only relevant for local module sources. + DisableFindUp bool + // 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. + AllowNotExists bool + // If set, error out if the ref string is not of the provided requireKind. + RequireKind ModuleSourceKind +} + +// Create a new module source instance from a source ref string +func (r *Query) ModuleSource(refString string, opts ...ModuleSourceOpts) *ModuleSource { + q := r.query.Select("moduleSource") + for i := len(opts) - 1; i >= 0; i-- { + // `refPin` optional argument + if !querybuilder.IsZeroValue(opts[i].RefPin) { + q = q.Arg("refPin", opts[i].RefPin) + } + // `disableFindUp` optional argument + if !querybuilder.IsZeroValue(opts[i].DisableFindUp) { + q = q.Arg("disableFindUp", opts[i].DisableFindUp) + } + // `allowNotExists` optional argument + if !querybuilder.IsZeroValue(opts[i].AllowNotExists) { + q = q.Arg("allowNotExists", opts[i].AllowNotExists) + } + // `requireKind` optional argument + if !querybuilder.IsZeroValue(opts[i].RequireKind) { + q = q.Arg("requireKind", opts[i].RequireKind) + } + } + q = q.Arg("refString", refString) + + return &ModuleSource{ + query: q, + } +} + +// Load any object by its ID. +func (r *Query) Node(ctx context.Context, id ID) (Node, error) { + q := r.query.Select("node") + q = q.Arg("id", id) + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &NodeClient{ + query: selectNode(q.Root(), *objectID, "Node"), + }, nil +} + +// Load a GraphQL introspection schema for merging. +func (r *Query) Schema(json JSON) *Schema { + q := r.query.Select("schema") + q = q.Arg("json", json) + + return &Schema{ + query: q, + } +} + +// SecretOpts contains options for Query.Secret +type SecretOpts struct { + // 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. + CacheKey string +} + +// Creates a new secret. +func (r *Query) Secret(uri string, opts ...SecretOpts) *Secret { + q := r.query.Select("secret") + for i := len(opts) - 1; i >= 0; i-- { + // `cacheKey` optional argument + if !querybuilder.IsZeroValue(opts[i].CacheKey) { + q = q.Arg("cacheKey", opts[i].CacheKey) + } + } + q = q.Arg("uri", uri) + + return &Secret{ + query: q, + } +} + +// 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. +func (r *Query) SetSecret(name string, plaintext string) *Secret { + q := r.query.Select("setSecret") + q = q.Arg("name", name) + q = q.Arg("plaintext", plaintext) + + return &Secret{ + query: q, + } +} + +// Creates source map metadata. +func (r *Query) SourceMap(filename string, line int, column int) *SourceMap { + q := r.query.Select("sourceMap") + q = q.Arg("filename", filename) + q = q.Arg("line", line) + q = q.Arg("column", column) + + return &SourceMap{ + query: q, + } +} + +// Create a new TypeDef. +func (r *Query) TypeDef() *TypeDef { + q := r.query.Select("typeDef") + + return &TypeDef{ + query: q, + } +} + +// Get the current Dagger Engine version. +func (r *Query) Version(ctx context.Context) (string, error) { + q := r.query.Select("version") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this Query as a Node. +// This is a local type conversion — no GraphQL call. +func (r *Query) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// An internal persistent bare git mirror. +type RemoteGitMirror struct { + query *querybuilder.Selection + + id *ID +} + +func (r *RemoteGitMirror) WithGraphQLQuery(q *querybuilder.Selection) *RemoteGitMirror { + return &RemoteGitMirror{ + query: q, + } +} + +// A unique identifier for this RemoteGitMirror. +func (r *RemoteGitMirror) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *RemoteGitMirror) XXX_GraphQLType() string { + return "RemoteGitMirror" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *RemoteGitMirror) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *RemoteGitMirror) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *RemoteGitMirror) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *RemoteGitMirror) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = RemoteGitMirror{query: selectNode(dag.query, id, "RemoteGitMirror")} + return nil +} + +// AsNode returns this RemoteGitMirror as a Node. +// This is a local type conversion — no GraphQL call. +func (r *RemoteGitMirror) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// The SDK config of the module. +type SDKConfig struct { + query *querybuilder.Selection + + debug *bool + id *ID + source *string +} + +func (r *SDKConfig) WithGraphQLQuery(q *querybuilder.Selection) *SDKConfig { + return &SDKConfig{ + query: q, + } +} + +// Whether to start the SDK runtime in debug mode with an interactive terminal. +func (r *SDKConfig) Debug(ctx context.Context) (bool, error) { + if r.debug != nil { + return *r.debug, nil + } + q := r.query.Select("debug") + + var response bool + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this SDKConfig. +func (r *SDKConfig) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *SDKConfig) XXX_GraphQLType() string { + return "SDKConfig" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *SDKConfig) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *SDKConfig) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *SDKConfig) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *SDKConfig) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = SDKConfig{query: selectNode(dag.query, id, "SDKConfig")} + return nil +} + +// Source of the SDK. Either a name of a builtin SDK or a module source ref string pointing to the SDK's implementation. +func (r *SDKConfig) Source(ctx context.Context) (string, error) { + if r.source != nil { + return *r.source, nil + } + q := r.query.Select("source") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this SDKConfig as a Node. +// This is a local type conversion — no GraphQL call. +func (r *SDKConfig) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A definition of a custom scalar defined in a Module. +type ScalarTypeDef struct { + query *querybuilder.Selection + + description *string + id *ID + name *string + sourceModuleName *string +} + +func (r *ScalarTypeDef) WithGraphQLQuery(q *querybuilder.Selection) *ScalarTypeDef { + return &ScalarTypeDef{ + query: q, + } +} + +// A doc string for the scalar, if any. +func (r *ScalarTypeDef) Description(ctx context.Context) (string, error) { + if r.description != nil { + return *r.description, nil + } + q := r.query.Select("description") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this ScalarTypeDef. +func (r *ScalarTypeDef) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *ScalarTypeDef) XXX_GraphQLType() string { + return "ScalarTypeDef" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *ScalarTypeDef) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *ScalarTypeDef) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *ScalarTypeDef) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *ScalarTypeDef) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = ScalarTypeDef{query: selectNode(dag.query, id, "ScalarTypeDef")} + return nil +} + +// The name of the scalar. +func (r *ScalarTypeDef) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// If this ScalarTypeDef is associated with a Module, the name of the module. Unset otherwise. +func (r *ScalarTypeDef) SourceModuleName(ctx context.Context) (string, error) { + if r.sourceModuleName != nil { + return *r.sourceModuleName, nil + } + q := r.query.Select("sourceModuleName") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this ScalarTypeDef as a Node. +// This is a local type conversion — no GraphQL call. +func (r *ScalarTypeDef) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A GraphQL introspection schema that can be inspected and merged. +type Schema struct { + query *querybuilder.Selection + + contents *JSON + id *ID +} +type WithSchemaFunc func(r *Schema) *Schema + +// With calls the provided function with current Schema. +// +// This is useful for reusability and readability by not breaking the calling chain. +func (r *Schema) With(f WithSchemaFunc) *Schema { + return f(r) +} + +func (r *Schema) WithGraphQLQuery(q *querybuilder.Selection) *Schema { + return &Schema{ + query: q, + } +} + +// Serialize the schema back to introspection JSON. +func (r *Schema) Contents(ctx context.Context) (JSON, error) { + if r.contents != nil { + return *r.contents, nil + } + q := r.query.Select("contents") + + var response JSON + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this Schema. +func (r *Schema) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Schema) XXX_GraphQLType() string { + return "Schema" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Schema) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Schema) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Schema) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Schema) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = Schema{query: selectNode(dag.query, id, "Schema")} + return nil +} + +// Merge a module's introspection-shaped type definitions into the schema, returning the combined schema. +func (r *Schema) Merge(moduleTypes JSON, moduleName string) *Schema { + q := r.query.Select("merge") + q = q.Arg("moduleTypes", moduleTypes) + q = q.Arg("moduleName", moduleName) + + return &Schema{ + query: q, + } +} + +// AsNode returns this Schema as a Node. +// This is a local type conversion — no GraphQL call. +func (r *Schema) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +type SearchResult struct { + query *querybuilder.Selection + + absoluteOffset *int + filePath *string + id *ID + lineNumber *int + matchedLines *string +} + +func (r *SearchResult) WithGraphQLQuery(q *querybuilder.Selection) *SearchResult { + return &SearchResult{ + query: q, + } +} + +// The byte offset of this line within the file. +func (r *SearchResult) AbsoluteOffset(ctx context.Context) (int, error) { + if r.absoluteOffset != nil { + return *r.absoluteOffset, nil + } + q := r.query.Select("absoluteOffset") + + var response int + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The path to the file that matched. +func (r *SearchResult) FilePath(ctx context.Context) (string, error) { + if r.filePath != nil { + return *r.filePath, nil + } + q := r.query.Select("filePath") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this SearchResult. +func (r *SearchResult) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *SearchResult) XXX_GraphQLType() string { + return "SearchResult" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *SearchResult) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *SearchResult) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *SearchResult) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *SearchResult) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = SearchResult{query: selectNode(dag.query, id, "SearchResult")} + return nil +} + +// The first line that matched. +func (r *SearchResult) LineNumber(ctx context.Context) (int, error) { + if r.lineNumber != nil { + return *r.lineNumber, nil + } + q := r.query.Select("lineNumber") + + var response int + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The line content that matched. +func (r *SearchResult) MatchedLines(ctx context.Context) (string, error) { + if r.matchedLines != nil { + return *r.matchedLines, nil + } + q := r.query.Select("matchedLines") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Sub-match positions and content within the matched lines. +func (r *SearchResult) Submatches(ctx context.Context) ([]SearchSubmatch, error) { + q := r.query.Select("submatches") + + q = q.Select("id") + + type submatches struct { + Id ID + } + + convert := func(fields []submatches) []SearchSubmatch { + out := []SearchSubmatch{} + + for i := range fields { + val := SearchSubmatch{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "SearchSubmatch") + out = append(out, val) + } + + return out + } + var response []submatches + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// AsNode returns this SearchResult as a Node. +// This is a local type conversion — no GraphQL call. +func (r *SearchResult) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +type SearchSubmatch struct { + query *querybuilder.Selection + + end *int + id *ID + start *int + text *string +} + +func (r *SearchSubmatch) WithGraphQLQuery(q *querybuilder.Selection) *SearchSubmatch { + return &SearchSubmatch{ + query: q, + } +} + +// The match's end offset within the matched lines. +func (r *SearchSubmatch) End(ctx context.Context) (int, error) { + if r.end != nil { + return *r.end, nil + } + q := r.query.Select("end") + + var response int + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this SearchSubmatch. +func (r *SearchSubmatch) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *SearchSubmatch) XXX_GraphQLType() string { + return "SearchSubmatch" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *SearchSubmatch) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *SearchSubmatch) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *SearchSubmatch) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *SearchSubmatch) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = SearchSubmatch{query: selectNode(dag.query, id, "SearchSubmatch")} + return nil +} + +// The match's start offset within the matched lines. +func (r *SearchSubmatch) Start(ctx context.Context) (int, error) { + if r.start != nil { + return *r.start, nil + } + q := r.query.Select("start") + + var response int + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The matched text. +func (r *SearchSubmatch) Text(ctx context.Context) (string, error) { + if r.text != nil { + return *r.text, nil + } + q := r.query.Select("text") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this SearchSubmatch as a Node. +// This is a local type conversion — no GraphQL call. +func (r *SearchSubmatch) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A reference to a secret value, which can be handled more safely than the value itself. +type Secret struct { + query *querybuilder.Selection + + id *ID + name *string + plaintext *string + uri *string +} + +func (r *Secret) WithGraphQLQuery(q *querybuilder.Selection) *Secret { + return &Secret{ + query: q, + } +} + +// A unique identifier for this Secret. +func (r *Secret) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Secret) XXX_GraphQLType() string { + return "Secret" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Secret) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Secret) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Secret) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Secret) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = Secret{query: selectNode(dag.query, id, "Secret")} + return nil +} + +// The name of this secret. +func (r *Secret) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The value of this secret. +func (r *Secret) Plaintext(ctx context.Context) (string, error) { + if r.plaintext != nil { + return *r.plaintext, nil + } + q := r.query.Select("plaintext") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The URI of this secret. +func (r *Secret) URI(ctx context.Context) (string, error) { + if r.uri != nil { + return *r.uri, nil + } + q := r.query.Select("uri") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this Secret as a Node. +// This is a local type conversion — no GraphQL call. +func (r *Secret) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A content-addressed service providing TCP connectivity. +type Service struct { + query *querybuilder.Selection + + endpoint *string + hostname *string + id *ID + start *ID + stop *ID + sync *ID + up *Void +} +type WithServiceFunc func(r *Service) *Service + +// With calls the provided function with current Service. +// +// This is useful for reusability and readability by not breaking the calling chain. +func (r *Service) With(f WithServiceFunc) *Service { + return f(r) +} + +func (r *Service) WithGraphQLQuery(q *querybuilder.Selection) *Service { + return &Service{ + query: q, + } +} + +// ServiceEndpointOpts contains options for Service.Endpoint +type ServiceEndpointOpts struct { + // The exposed port number for the endpoint + Port int + // Return a URL with the given scheme, eg. http for http:// + Scheme string +} + +// 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. +func (r *Service) Endpoint(ctx context.Context, opts ...ServiceEndpointOpts) (string, error) { + if r.endpoint != nil { + return *r.endpoint, nil + } + q := r.query.Select("endpoint") + for i := len(opts) - 1; i >= 0; i-- { + // `port` optional argument + if !querybuilder.IsZeroValue(opts[i].Port) { + q = q.Arg("port", opts[i].Port) + } + // `scheme` optional argument + if !querybuilder.IsZeroValue(opts[i].Scheme) { + q = q.Arg("scheme", opts[i].Scheme) + } + } + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Retrieves a hostname which can be used by clients to reach this container. +func (r *Service) Hostname(ctx context.Context) (string, error) { + if r.hostname != nil { + return *r.hostname, nil + } + q := r.query.Select("hostname") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this Service. +func (r *Service) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Service) XXX_GraphQLType() string { + return "Service" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Service) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Service) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Service) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Service) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = Service{query: selectNode(dag.query, id, "Service")} + return nil +} + +// Retrieves the list of ports provided by the service. +func (r *Service) Ports(ctx context.Context) ([]Port, error) { + q := r.query.Select("ports") + + q = q.Select("id") + + type ports struct { + Id ID + } + + convert := func(fields []ports) []Port { + out := []Port{} + + for i := range fields { + val := Port{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "Port") + out = append(out, val) + } + + return out + } + var response []ports + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// Start the service and wait for its health checks to succeed. +// +// Services bound to a Container do not need to be manually started. +func (r *Service) Start(ctx context.Context) (*Service, error) { + q := r.query.Select("start") + + var id ID + if err := q.Bind(&id).Execute(ctx); err != nil { + return nil, err + } + return &Service{ + query: selectNode(q.Root(), id, "Service"), + }, nil +} + +// ServiceStopOpts contains options for Service.Stop +type ServiceStopOpts struct { + // Immediately kill the service without waiting for a graceful exit + Kill bool +} + +// Stop the service. +func (r *Service) Stop(ctx context.Context, opts ...ServiceStopOpts) (*Service, error) { + q := r.query.Select("stop") + for i := len(opts) - 1; i >= 0; i-- { + // `kill` optional argument + if !querybuilder.IsZeroValue(opts[i].Kill) { + q = q.Arg("kill", opts[i].Kill) + } + } + + var id ID + if err := q.Bind(&id).Execute(ctx); err != nil { + return nil, err + } + return &Service{ + query: selectNode(q.Root(), id, "Service"), + }, nil +} + +// Forces evaluation of the pipeline in the engine. +func (r *Service) Sync(ctx context.Context) (*Service, error) { + q := r.query.Select("sync") + + var id ID + if err := q.Bind(&id).Execute(ctx); err != nil { + return nil, err + } + return &Service{ + query: selectNode(q.Root(), id, "Service"), + }, nil +} + +// ServiceTerminalOpts contains options for Service.Terminal +type ServiceTerminalOpts struct { + Cmd []string +} + +func (r *Service) Terminal(opts ...ServiceTerminalOpts) *Service { + q := r.query.Select("terminal") + for i := len(opts) - 1; i >= 0; i-- { + // `cmd` optional argument + if !querybuilder.IsZeroValue(opts[i].Cmd) { + q = q.Arg("cmd", opts[i].Cmd) + } + } + + return &Service{ + query: q, + } +} + +// ServiceUpOpts contains options for Service.Up +type ServiceUpOpts struct { + // List of frontend/backend port mappings to forward. + // + // Frontend is the port accepting traffic on the host, backend is the service port. + Ports []PortForward + // Bind each tunnel port to a random port on the host. + Random bool +} + +// Creates a tunnel that forwards traffic from the caller's network to this service. +func (r *Service) Up(ctx context.Context, opts ...ServiceUpOpts) error { + if r.up != nil { + return nil + } + q := r.query.Select("up") + for i := len(opts) - 1; i >= 0; i-- { + // `ports` optional argument + if !querybuilder.IsZeroValue(opts[i].Ports) { + q = q.Arg("ports", opts[i].Ports) + } + // `random` optional argument + if !querybuilder.IsZeroValue(opts[i].Random) { + q = q.Arg("random", opts[i].Random) + } + } + + return q.Execute(ctx) +} + +// Configures a hostname which can be used by clients within the session to reach this container. +func (r *Service) WithHostname(hostname string) *Service { + q := r.query.Select("withHostname") + q = q.Arg("hostname", hostname) + + return &Service{ + query: q, + } +} + +// AsNode returns this Service as a Node. +// This is a local type conversion — no GraphQL call. +func (r *Service) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// AsSyncer returns this Service as a Syncer. +// This is a local type conversion — no GraphQL call. +func (r *Service) AsSyncer() Syncer { + return &SyncerClient{ + query: r.query, + } +} + +// A Unix or TCP/IP socket that can be mounted into a container. +type Socket struct { + query *querybuilder.Selection + + id *ID +} + +func (r *Socket) WithGraphQLQuery(q *querybuilder.Selection) *Socket { + return &Socket{ + query: q, + } +} + +// A unique identifier for this Socket. +func (r *Socket) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Socket) XXX_GraphQLType() string { + return "Socket" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Socket) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Socket) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Socket) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Socket) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = Socket{query: selectNode(dag.query, id, "Socket")} + return nil +} + +// AsNode returns this Socket as a Node. +// This is a local type conversion — no GraphQL call. +func (r *Socket) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// Source location information. +type SourceMap struct { + query *querybuilder.Selection + + column *int + filename *string + id *ID + line *int + module *string + url *string +} + +func (r *SourceMap) WithGraphQLQuery(q *querybuilder.Selection) *SourceMap { + return &SourceMap{ + query: q, + } +} + +// The column number within the line. +func (r *SourceMap) Column(ctx context.Context) (int, error) { + if r.column != nil { + return *r.column, nil + } + q := r.query.Select("column") + + var response int + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The filename from the module source. +func (r *SourceMap) Filename(ctx context.Context) (string, error) { + if r.filename != nil { + return *r.filename, nil + } + q := r.query.Select("filename") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this SourceMap. +func (r *SourceMap) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *SourceMap) XXX_GraphQLType() string { + return "SourceMap" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *SourceMap) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *SourceMap) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *SourceMap) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *SourceMap) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = SourceMap{query: selectNode(dag.query, id, "SourceMap")} + return nil +} + +// The line number within the filename. +func (r *SourceMap) Line(ctx context.Context) (int, error) { + if r.line != nil { + return *r.line, nil + } + q := r.query.Select("line") + + var response int + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The module dependency this was declared in. +func (r *SourceMap) Module(ctx context.Context) (string, error) { + if r.module != nil { + return *r.module, nil + } + q := r.query.Select("module") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The URL to the file, if any. This can be used to link to the source map in the browser. +func (r *SourceMap) URL(ctx context.Context) (string, error) { + if r.url != nil { + return *r.url, nil + } + q := r.query.Select("url") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this SourceMap as a Node. +// This is a local type conversion — no GraphQL call. +func (r *SourceMap) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A file or directory status object. +type Stat struct { + query *querybuilder.Selection + + fileType *FileType + id *ID + name *string + permissions *int + size *int +} + +func (r *Stat) WithGraphQLQuery(q *querybuilder.Selection) *Stat { + return &Stat{ + query: q, + } +} + +// file type +func (r *Stat) FileType(ctx context.Context) (FileType, error) { + if r.fileType != nil { + return *r.fileType, nil + } + q := r.query.Select("fileType") + + var response FileType + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this Stat. +func (r *Stat) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Stat) XXX_GraphQLType() string { + return "Stat" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Stat) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Stat) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Stat) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Stat) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = Stat{query: selectNode(dag.query, id, "Stat")} + return nil +} + +// file name +func (r *Stat) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// permission bits +func (r *Stat) Permissions(ctx context.Context) (int, error) { + if r.permissions != nil { + return *r.permissions, nil + } + q := r.query.Select("permissions") + + var response int + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// file size +func (r *Stat) Size(ctx context.Context) (int, error) { + if r.size != nil { + return *r.size, nil + } + q := r.query.Select("size") + + var response int + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this Stat as a Node. +// This is a local type conversion — no GraphQL call. +func (r *Stat) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// An interactive terminal that clients can connect to. +type Terminal struct { + query *querybuilder.Selection + + id *ID + sync *ID +} + +func (r *Terminal) WithGraphQLQuery(q *querybuilder.Selection) *Terminal { + return &Terminal{ + query: q, + } +} + +// A unique identifier for this Terminal. +func (r *Terminal) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Terminal) XXX_GraphQLType() string { + return "Terminal" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Terminal) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Terminal) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Terminal) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Terminal) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = Terminal{query: selectNode(dag.query, id, "Terminal")} + return nil +} + +// Forces evaluation of the pipeline in the engine. +// +// It doesn't run the default command if no exec has been set. +func (r *Terminal) Sync(ctx context.Context) (*Terminal, error) { + q := r.query.Select("sync") + + var id ID + if err := q.Bind(&id).Execute(ctx); err != nil { + return nil, err + } + return &Terminal{ + query: selectNode(q.Root(), id, "Terminal"), + }, nil +} + +// AsNode returns this Terminal as a Node. +// This is a local type conversion — no GraphQL call. +func (r *Terminal) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// AsSyncer returns this Terminal as a Syncer. +// This is a local type conversion — no GraphQL call. +func (r *Terminal) AsSyncer() Syncer { + return &SyncerClient{ + query: r.query, + } +} + +// A definition of a parameter or return type in a Module. +type TypeDef struct { + query *querybuilder.Selection + + id *ID + kind *TypeDefKind + name *string + optional *bool +} +type WithTypeDefFunc func(r *TypeDef) *TypeDef + +// With calls the provided function with current TypeDef. +// +// This is useful for reusability and readability by not breaking the calling chain. +func (r *TypeDef) With(f WithTypeDefFunc) *TypeDef { + return f(r) +} + +func (r *TypeDef) WithGraphQLQuery(q *querybuilder.Selection) *TypeDef { + return &TypeDef{ + query: q, + } +} + +// If kind is ENUM, the enum-specific type definition. If kind is not ENUM, this will be null. +func (r *TypeDef) AsEnum(ctx context.Context) (*EnumTypeDef, error) { + q := r.query.Select("asEnum") + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &EnumTypeDef{ + query: selectNode(q.Root(), *objectID, "EnumTypeDef"), + }, nil +} + +// If kind is INPUT, the input-specific type definition. If kind is not INPUT, this will be null. +func (r *TypeDef) AsInput(ctx context.Context) (*InputTypeDef, error) { + q := r.query.Select("asInput") + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &InputTypeDef{ + query: selectNode(q.Root(), *objectID, "InputTypeDef"), + }, nil +} + +// If kind is INTERFACE, the interface-specific type definition. If kind is not INTERFACE, this will be null. +func (r *TypeDef) AsInterface(ctx context.Context) (*InterfaceTypeDef, error) { + q := r.query.Select("asInterface") + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &InterfaceTypeDef{ + query: selectNode(q.Root(), *objectID, "InterfaceTypeDef"), + }, nil +} + +// If kind is LIST, the list-specific type definition. If kind is not LIST, this will be null. +func (r *TypeDef) AsList(ctx context.Context) (*ListTypeDef, error) { + q := r.query.Select("asList") + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &ListTypeDef{ + query: selectNode(q.Root(), *objectID, "ListTypeDef"), + }, nil +} + +// If kind is OBJECT, the object-specific type definition. If kind is not OBJECT, this will be null. +func (r *TypeDef) AsObject(ctx context.Context) (*ObjectTypeDef, error) { + q := r.query.Select("asObject") + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &ObjectTypeDef{ + query: selectNode(q.Root(), *objectID, "ObjectTypeDef"), + }, nil +} + +// If kind is SCALAR, the scalar-specific type definition. If kind is not SCALAR, this will be null. +func (r *TypeDef) AsScalar(ctx context.Context) (*ScalarTypeDef, error) { + q := r.query.Select("asScalar") + + q = q.Select("id") + var objectID *ID + if err := q.Bind(&objectID).Execute(ctx); err != nil { + return nil, err + } + if objectID == nil { + return nil, nil + } + return &ScalarTypeDef{ + query: selectNode(q.Root(), *objectID, "ScalarTypeDef"), + }, nil +} + +// A unique identifier for this TypeDef. +func (r *TypeDef) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *TypeDef) XXX_GraphQLType() string { + return "TypeDef" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *TypeDef) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *TypeDef) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *TypeDef) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *TypeDef) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = TypeDef{query: selectNode(dag.query, id, "TypeDef")} + return nil +} + +// The kind of type this is (e.g. primitive, list, object). +func (r *TypeDef) Kind(ctx context.Context) (TypeDefKind, error) { + if r.kind != nil { + return *r.kind, nil + } + q := r.query.Select("kind") + + var response TypeDefKind + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The canonical non-optional name of the type. +func (r *TypeDef) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Whether this type can be set to null. Defaults to false. +func (r *TypeDef) Optional(ctx context.Context) (bool, error) { + if r.optional != nil { + return *r.optional, nil + } + q := r.query.Select("optional") + + var response bool + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Adds a function for constructing a new instance of an Object TypeDef, failing if the type is not an object. +func (r *TypeDef) WithConstructor(function *Function) *TypeDef { + assertNotNil("function", function) + q := r.query.Select("withConstructor") + q = q.Arg("function", function) + + return &TypeDef{ + query: q, + } +} + +// TypeDefWithEnumOpts contains options for TypeDef.WithEnum +type TypeDefWithEnumOpts struct { + // A doc string for the enum, if any + Description string + // The source map for the enum definition. + SourceMap *SourceMap +} + +// 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. +func (r *TypeDef) WithEnum(name string, opts ...TypeDefWithEnumOpts) *TypeDef { + q := r.query.Select("withEnum") + for i := len(opts) - 1; i >= 0; i-- { + // `description` optional argument + if !querybuilder.IsZeroValue(opts[i].Description) { + q = q.Arg("description", opts[i].Description) + } + // `sourceMap` optional argument + if !querybuilder.IsZeroValue(opts[i].SourceMap) { + q = q.Arg("sourceMap", opts[i].SourceMap) + } + } + q = q.Arg("name", name) + + return &TypeDef{ + query: q, + } +} + +// TypeDefWithEnumMemberOpts contains options for TypeDef.WithEnumMember +type TypeDefWithEnumMemberOpts struct { + // The value of the member in the enum + Value string + // A doc string for the member, if any + Description string + // The source map for the enum member definition. + SourceMap *SourceMap + // If deprecated, the reason or migration path. + Deprecated string +} + +// Adds a static value for an Enum TypeDef, failing if the type is not an enum. +func (r *TypeDef) WithEnumMember(name string, opts ...TypeDefWithEnumMemberOpts) *TypeDef { + q := r.query.Select("withEnumMember") + for i := len(opts) - 1; i >= 0; i-- { + // `value` optional argument + if !querybuilder.IsZeroValue(opts[i].Value) { + q = q.Arg("value", opts[i].Value) + } + // `description` optional argument + if !querybuilder.IsZeroValue(opts[i].Description) { + q = q.Arg("description", opts[i].Description) + } + // `sourceMap` optional argument + if !querybuilder.IsZeroValue(opts[i].SourceMap) { + q = q.Arg("sourceMap", opts[i].SourceMap) + } + // `deprecated` optional argument + if !querybuilder.IsZeroValue(opts[i].Deprecated) { + q = q.Arg("deprecated", opts[i].Deprecated) + } + } + q = q.Arg("name", name) + + return &TypeDef{ + query: q, + } +} + +// TypeDefWithEnumValueOpts contains options for TypeDef.WithEnumValue +type TypeDefWithEnumValueOpts struct { + // A doc string for the value, if any + Description string + // The source map for the enum value definition. + SourceMap *SourceMap + // If deprecated, the reason or migration path. + Deprecated string +} + +// Adds a static value for an Enum TypeDef, failing if the type is not an enum. +// +// Deprecated: Use WithEnumMember instead +func (r *TypeDef) WithEnumValue(value string, opts ...TypeDefWithEnumValueOpts) *TypeDef { + q := r.query.Select("withEnumValue") + for i := len(opts) - 1; i >= 0; i-- { + // `description` optional argument + if !querybuilder.IsZeroValue(opts[i].Description) { + q = q.Arg("description", opts[i].Description) + } + // `sourceMap` optional argument + if !querybuilder.IsZeroValue(opts[i].SourceMap) { + q = q.Arg("sourceMap", opts[i].SourceMap) + } + // `deprecated` optional argument + if !querybuilder.IsZeroValue(opts[i].Deprecated) { + q = q.Arg("deprecated", opts[i].Deprecated) + } + } + q = q.Arg("value", value) + + return &TypeDef{ + query: q, + } +} + +// TypeDefWithFieldOpts contains options for TypeDef.WithField +type TypeDefWithFieldOpts struct { + // A doc string for the field, if any + Description string + // The source map for the field definition. + SourceMap *SourceMap + // If deprecated, the reason or migration path. + Deprecated string +} + +// Adds a static field for an Object TypeDef, failing if the type is not an object. +func (r *TypeDef) WithField(name string, typeDef *TypeDef, opts ...TypeDefWithFieldOpts) *TypeDef { + assertNotNil("typeDef", typeDef) + q := r.query.Select("withField") + for i := len(opts) - 1; i >= 0; i-- { + // `description` optional argument + if !querybuilder.IsZeroValue(opts[i].Description) { + q = q.Arg("description", opts[i].Description) + } + // `sourceMap` optional argument + if !querybuilder.IsZeroValue(opts[i].SourceMap) { + q = q.Arg("sourceMap", opts[i].SourceMap) + } + // `deprecated` optional argument + if !querybuilder.IsZeroValue(opts[i].Deprecated) { + q = q.Arg("deprecated", opts[i].Deprecated) + } + } + q = q.Arg("name", name) + q = q.Arg("typeDef", typeDef) + + return &TypeDef{ + query: q, + } +} + +// Adds a function for an Object or Interface TypeDef, failing if the type is not one of those kinds. +func (r *TypeDef) WithFunction(function *Function) *TypeDef { + assertNotNil("function", function) + q := r.query.Select("withFunction") + q = q.Arg("function", function) + + return &TypeDef{ + query: q, + } +} + +// TypeDefWithInterfaceOpts contains options for TypeDef.WithInterface +type TypeDefWithInterfaceOpts struct { + Description string + + SourceMap *SourceMap +} + +// Returns a TypeDef of kind Interface with the provided name. +func (r *TypeDef) WithInterface(name string, opts ...TypeDefWithInterfaceOpts) *TypeDef { + q := r.query.Select("withInterface") + for i := len(opts) - 1; i >= 0; i-- { + // `description` optional argument + if !querybuilder.IsZeroValue(opts[i].Description) { + q = q.Arg("description", opts[i].Description) + } + // `sourceMap` optional argument + if !querybuilder.IsZeroValue(opts[i].SourceMap) { + q = q.Arg("sourceMap", opts[i].SourceMap) + } + } + q = q.Arg("name", name) + + return &TypeDef{ + query: q, + } +} + +// Sets the kind of the type. +func (r *TypeDef) WithKind(kind TypeDefKind) *TypeDef { + q := r.query.Select("withKind") + q = q.Arg("kind", kind) + + return &TypeDef{ + query: q, + } +} + +// Returns a TypeDef of kind List with the provided type for its elements. +func (r *TypeDef) WithListOf(elementType *TypeDef) *TypeDef { + assertNotNil("elementType", elementType) + q := r.query.Select("withListOf") + q = q.Arg("elementType", elementType) + + return &TypeDef{ + query: q, + } +} + +// TypeDefWithObjectOpts contains options for TypeDef.WithObject +type TypeDefWithObjectOpts struct { + Description string + + SourceMap *SourceMap + + Deprecated string +} + +// 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. +func (r *TypeDef) WithObject(name string, opts ...TypeDefWithObjectOpts) *TypeDef { + q := r.query.Select("withObject") + for i := len(opts) - 1; i >= 0; i-- { + // `description` optional argument + if !querybuilder.IsZeroValue(opts[i].Description) { + q = q.Arg("description", opts[i].Description) + } + // `sourceMap` optional argument + if !querybuilder.IsZeroValue(opts[i].SourceMap) { + q = q.Arg("sourceMap", opts[i].SourceMap) + } + // `deprecated` optional argument + if !querybuilder.IsZeroValue(opts[i].Deprecated) { + q = q.Arg("deprecated", opts[i].Deprecated) + } + } + q = q.Arg("name", name) + + return &TypeDef{ + query: q, + } +} + +// Sets whether this type can be set to null. +func (r *TypeDef) WithOptional(optional bool) *TypeDef { + q := r.query.Select("withOptional") + q = q.Arg("optional", optional) + + return &TypeDef{ + query: q, + } +} + +// TypeDefWithScalarOpts contains options for TypeDef.WithScalar +type TypeDefWithScalarOpts struct { + Description string +} + +// Returns a TypeDef of kind Scalar with the provided name. +func (r *TypeDef) WithScalar(name string, opts ...TypeDefWithScalarOpts) *TypeDef { + q := r.query.Select("withScalar") + for i := len(opts) - 1; i >= 0; i-- { + // `description` optional argument + if !querybuilder.IsZeroValue(opts[i].Description) { + q = q.Arg("description", opts[i].Description) + } + } + q = q.Arg("name", name) + + return &TypeDef{ + query: q, + } +} + +// AsNode returns this TypeDef as a Node. +// This is a local type conversion — no GraphQL call. +func (r *TypeDef) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +type Up struct { + query *querybuilder.Selection + + description *string + id *ID + name *string +} +type WithUpFunc func(r *Up) *Up + +// With calls the provided function with current Up. +// +// This is useful for reusability and readability by not breaking the calling chain. +func (r *Up) With(f WithUpFunc) *Up { + return f(r) +} + +func (r *Up) WithGraphQLQuery(q *querybuilder.Selection) *Up { + return &Up{ + query: q, + } +} + +// The description of the service +func (r *Up) Description(ctx context.Context) (string, error) { + if r.description != nil { + return *r.description, nil + } + q := r.query.Select("description") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this Up. +func (r *Up) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Up) XXX_GraphQLType() string { + return "Up" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Up) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Up) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Up) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Up) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = Up{query: selectNode(dag.query, id, "Up")} + return nil +} + +// Return the fully qualified name of the service +func (r *Up) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The original module in which the service has been defined +func (r *Up) OriginalModule() *Module { + q := r.query.Select("originalModule") + + return &Module{ + query: q, + } +} + +// The path of the service within its module +func (r *Up) Path(ctx context.Context) ([]string, error) { + q := r.query.Select("path") + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Execute the service function +func (r *Up) Run() *Up { + q := r.query.Select("run") + + return &Up{ + query: q, + } +} + +// AsNode returns this Up as a Node. +// This is a local type conversion — no GraphQL call. +func (r *Up) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +type UpGroup struct { + query *querybuilder.Selection + + id *ID +} +type WithUpGroupFunc func(r *UpGroup) *UpGroup + +// With calls the provided function with current UpGroup. +// +// This is useful for reusability and readability by not breaking the calling chain. +func (r *UpGroup) With(f WithUpGroupFunc) *UpGroup { + return f(r) +} + +func (r *UpGroup) WithGraphQLQuery(q *querybuilder.Selection) *UpGroup { + return &UpGroup{ + query: q, + } +} + +// A unique identifier for this UpGroup. +func (r *UpGroup) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *UpGroup) XXX_GraphQLType() string { + return "UpGroup" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *UpGroup) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *UpGroup) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *UpGroup) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *UpGroup) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = UpGroup{query: selectNode(dag.query, id, "UpGroup")} + return nil +} + +// Return a list of individual services and their details +func (r *UpGroup) List(ctx context.Context) ([]Up, error) { + q := r.query.Select("list") + + q = q.Select("id") + + type list struct { + Id ID + } + + convert := func(fields []list) []Up { + out := []Up{} + + for i := range fields { + val := Up{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "Up") + out = append(out, val) + } + + return out + } + var response []list + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// Execute all selected service functions +func (r *UpGroup) Run() *UpGroup { + q := r.query.Select("run") + + return &UpGroup{ + query: q, + } +} + +// AsNode returns this UpGroup as a Node. +// This is a local type conversion — no GraphQL call. +func (r *UpGroup) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A filesystem volume that can be mounted into containers. +type Volume struct { + query *querybuilder.Selection + + id *ID +} + +func (r *Volume) WithGraphQLQuery(q *querybuilder.Selection) *Volume { + return &Volume{ + query: q, + } +} + +// A unique identifier for this Volume. +func (r *Volume) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Volume) XXX_GraphQLType() string { + return "Volume" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Volume) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Volume) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Volume) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Volume) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = Volume{query: selectNode(dag.query, id, "Volume")} + return nil +} + +// AsNode returns this Volume as a Node. +// This is a local type conversion — no GraphQL call. +func (r *Volume) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A Dagger workspace detected from the current working directory or constructed from a Directory. +type Workspace struct { + query *querybuilder.Selection + + address *string + configFile *string + configRead *string + cwd *string + export *Void + findUp *string + id *ID +} +type WithWorkspaceFunc func(r *Workspace) *Workspace + +// With calls the provided function with current Workspace. +// +// This is useful for reusability and readability by not breaking the calling chain. +func (r *Workspace) With(f WithWorkspaceFunc) *Workspace { + return f(r) +} + +func (r *Workspace) WithGraphQLQuery(q *querybuilder.Selection) *Workspace { + return &Workspace{ + query: q, + } +} + +// Canonical Dagger address of the workspace location, or an opaque identity for synthetic workspaces. +func (r *Workspace) Address(ctx context.Context) (string, error) { + if r.address != nil { + return *r.address, nil + } + q := r.query.Select("address") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// WorkspaceAgentsOpts contains options for Workspace.Agents +type WorkspaceAgentsOpts struct { + // Only include agents matching the specified patterns + Include []string +} + +// Return all agent middlewares from modules loaded in the workspace. +func (r *Workspace) Agents(opts ...WorkspaceAgentsOpts) *AgentGroup { + q := r.query.Select("agents") + for i := len(opts) - 1; i >= 0; i-- { + // `include` optional argument + if !querybuilder.IsZeroValue(opts[i].Include) { + q = q.Arg("include", opts[i].Include) + } + } + + return &AgentGroup{ + query: q, + } +} + +// WorkspaceChangesOpts contains options for Workspace.Changes +type WorkspaceChangesOpts struct { + // An earlier workspace state to compare against. + From *Workspace +} + +// Return this workspace's changes, with paths relative to its working directory. +// +// Pass from to compare against an earlier workspace state. Omitting it preserves the cumulative behavior used by clients from before this argument was added. +func (r *Workspace) Changes(opts ...WorkspaceChangesOpts) *Changeset { + q := r.query.Select("changes") + for i := len(opts) - 1; i >= 0; i-- { + // `from` optional argument + if !querybuilder.IsZeroValue(opts[i].From) { + q = q.Arg("from", opts[i].From) + } + } + + return &Changeset{ + query: q, + } +} + +// WorkspaceChecksOpts contains options for Workspace.Checks +type WorkspaceChecksOpts struct { + // Only include checks matching the specified patterns + Include []string + // Skip checks matching the specified patterns + Skip []string + // When true, only return annotated check functions; exclude generate-as-checks + NoGenerate bool + // When true, only return generate-as-checks; exclude annotated check functions + OnlyGenerate bool +} + +// Return all checks from modules loaded in the workspace. +func (r *Workspace) Checks(opts ...WorkspaceChecksOpts) *CheckGroup { + q := r.query.Select("checks") + for i := len(opts) - 1; i >= 0; i-- { + // `include` optional argument + if !querybuilder.IsZeroValue(opts[i].Include) { + q = q.Arg("include", opts[i].Include) + } + // `skip` optional argument + if !querybuilder.IsZeroValue(opts[i].Skip) { + q = q.Arg("skip", opts[i].Skip) + } + // `noGenerate` optional argument + if !querybuilder.IsZeroValue(opts[i].NoGenerate) { + q = q.Arg("noGenerate", opts[i].NoGenerate) + } + // `onlyGenerate` optional argument + if !querybuilder.IsZeroValue(opts[i].OnlyGenerate) { + q = q.Arg("onlyGenerate", opts[i].OnlyGenerate) + } + } + + return &CheckGroup{ + query: q, + } +} + +// Selected native workspace config file relative to the workspace cwd, if any. +func (r *Workspace) ConfigFile(ctx context.Context) (string, error) { + if r.configFile != nil { + return *r.configFile, nil + } + q := r.query.Select("configFile") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// WorkspaceConfigReadOpts contains options for Workspace.ConfigRead +type WorkspaceConfigReadOpts struct { + // Dotted key path (e.g. modules.greeter.source). Empty for full config. + Key string +} + +// Read a configuration value from dagger.toml. +// +// If key is empty, returns the full config. +// +// If key points to a scalar, returns the value. +// +// If key points to a table, returns flattened dotted-key output. +func (r *Workspace) ConfigRead(ctx context.Context, opts ...WorkspaceConfigReadOpts) (string, error) { + if r.configRead != nil { + return *r.configRead, nil + } + q := r.query.Select("configRead") + for i := len(opts) - 1; i >= 0; i-- { + // `key` optional argument + if !querybuilder.IsZeroValue(opts[i].Key) { + q = q.Arg("key", opts[i].Key) + } + } + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Current location within the workspace root. +// +// The workspace root is returned as "/". +// +// Relative paths in workspace APIs resolve from here. +func (r *Workspace) Cwd(ctx context.Context) (string, error) { + if r.cwd != nil { + return *r.cwd, nil + } + q := r.query.Select("cwd") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// WorkspaceDirectoryOpts contains options for Workspace.Directory +type WorkspaceDirectoryOpts struct { + // Exclude artifacts that match the given pattern (e.g., ["node_modules/", ".git*"]). + Exclude []string + // Include only artifacts that match the given pattern (e.g., ["app/", "package.*"]). + Include []string + // Apply .gitignore filter rules inside the directory. + Gitignore bool +} + +// Returns a Directory from the workspace. +// +// Relative paths resolve from the workspace cwd. Absolute paths resolve from the workspace root. +func (r *Workspace) Directory(path string, opts ...WorkspaceDirectoryOpts) *Directory { + q := r.query.Select("directory") + for i := len(opts) - 1; i >= 0; i-- { + // `exclude` optional argument + if !querybuilder.IsZeroValue(opts[i].Exclude) { + q = q.Arg("exclude", opts[i].Exclude) + } + // `include` optional argument + if !querybuilder.IsZeroValue(opts[i].Include) { + q = q.Arg("include", opts[i].Include) + } + // `gitignore` optional argument + if !querybuilder.IsZeroValue(opts[i].Gitignore) { + q = q.Arg("gitignore", opts[i].Gitignore) + } + } + q = q.Arg("path", path) + + return &Directory{ + query: q, + } +} + +// List named environments defined in the workspace configuration. +func (r *Workspace) EnvList(ctx context.Context) ([]string, error) { + q := r.query.Select("envList") + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Write this workspace's pending changes to its local Git workspace. +func (r *Workspace) Export(ctx context.Context) error { + if r.export != nil { + return nil + } + q := r.query.Select("export") + + return q.Execute(ctx) +} + +// Returns a File from the workspace. +// +// Relative paths resolve from the workspace cwd. Absolute paths resolve from the workspace root. +func (r *Workspace) File(path string) *File { + q := r.query.Select("file") + q = q.Arg("path", path) + + return &File{ + query: q, + } +} + +// WorkspaceFindRootsOpts contains options for Workspace.FindRoots +type WorkspaceFindRootsOpts struct { + // Directory to start from. Relative paths resolve from the workspace cwd. + // + // Default: "." + Start string + // Glob patterns pruning the walk below start (e.g. ["**/node_modules/**"]). + Exclude []string +} + +// Find project roots marked by any of the given filenames, starting from a path relative to the workspace cwd. +// +// Returns cwd-relative directory paths for every marked directory at or below start, plus the nearest marked ancestor when start itself is not marked. +// +// Each returned path is usable as-is with other workspace APIs, e.g. directory(path). +func (r *Workspace) FindRoots(ctx context.Context, markers []string, opts ...WorkspaceFindRootsOpts) ([]string, error) { + q := r.query.Select("findRoots") + for i := len(opts) - 1; i >= 0; i-- { + // `start` optional argument + if !querybuilder.IsZeroValue(opts[i].Start) { + q = q.Arg("start", opts[i].Start) + } + // `exclude` optional argument + if !querybuilder.IsZeroValue(opts[i].Exclude) { + q = q.Arg("exclude", opts[i].Exclude) + } + } + q = q.Arg("markers", markers) + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// WorkspaceFindUpOpts contains options for Workspace.FindUp +type WorkspaceFindUpOpts struct { + // Path to start the search from. Relative paths resolve from the workspace cwd; absolute paths resolve from the workspace root. + // + // Default: "." + From string +} + +// Search for a file or directory by walking up from the start path within the workspace. +// +// Returns the absolute workspace path if found, or null if not found. +// +// Relative start paths resolve from the workspace cwd. +// +// The search stops at the workspace root and will not traverse above it. +func (r *Workspace) FindUp(ctx context.Context, name string, opts ...WorkspaceFindUpOpts) (string, error) { + if r.findUp != nil { + return *r.findUp, nil + } + q := r.query.Select("findUp") + for i := len(opts) - 1; i >= 0; i-- { + // `from` optional argument + if !querybuilder.IsZeroValue(opts[i].From) { + q = q.Arg("from", opts[i].From) + } + } + q = q.Arg("name", name) + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// WorkspaceGeneratorsOpts contains options for Workspace.Generators +type WorkspaceGeneratorsOpts struct { + // Only include generators matching the specified patterns + Include []string +} + +// Return all generators from modules loaded in the workspace. +func (r *Workspace) Generators(opts ...WorkspaceGeneratorsOpts) *GeneratorGroup { + q := r.query.Select("generators") + for i := len(opts) - 1; i >= 0; i-- { + // `include` optional argument + if !querybuilder.IsZeroValue(opts[i].Include) { + q = q.Arg("include", opts[i].Include) + } + } + + return &GeneratorGroup{ + query: q, + } +} + +// Git state for this workspace. Errors if the workspace is not in a git repository. +func (r *Workspace) Git() *WorkspaceGit { + q := r.query.Select("git") + + return &WorkspaceGit{ + query: q, + } +} + +// Returns a list of files and directories that match the given pattern. +// +// Patterns match paths relative to the workspace root. +func (r *Workspace) Glob(ctx context.Context, pattern string) ([]string, error) { + q := r.query.Select("glob") + q = q.Arg("pattern", pattern) + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this Workspace. +func (r *Workspace) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *Workspace) XXX_GraphQLType() string { + return "Workspace" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *Workspace) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *Workspace) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *Workspace) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *Workspace) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = Workspace{query: selectNode(dag.query, id, "Workspace")} + return nil +} + +// Plan the explicit migration needed for the current workspace. +// +// The returned plan has an empty changeset and no steps when no migration is needed. +func (r *Workspace) Migrate() *WorkspaceMigration { + q := r.query.Select("migrate") + + return &WorkspaceMigration{ + query: q, + } +} + +// Return a module defined in the workspace configuration. +// +// Reflects the selected env's effective view. +func (r *Workspace) Module(name string) *WorkspaceModule { + q := r.query.Select("module") + q = q.Arg("name", name) + + return &WorkspaceModule{ + query: q, + } +} + +// Load a module source from a path within the workspace. +// +// Relative paths (e.g., "foo") resolve from the workspace cwd; absolute paths (e.g., "/foo") resolve from the workspace root. +// +// Fails if the path does not point to an initialized module. +func (r *Workspace) ModuleSource(path string) *ModuleSource { + q := r.query.Select("moduleSource") + q = q.Arg("path", path) + + return &ModuleSource{ + query: q, + } +} + +// List modules defined in the workspace configuration. +// +// Reflects the selected env's effective view. +func (r *Workspace) Modules(ctx context.Context) ([]WorkspaceModule, error) { + q := r.query.Select("modules") + + q = q.Select("id") + + type modules struct { + Id ID + } + + convert := func(fields []modules) []WorkspaceModule { + out := []WorkspaceModule{} + + for i := range fields { + val := WorkspaceModule{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "WorkspaceModule") + out = append(out, val) + } + + return out + } + var response []modules + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// Return this workspace with its cached host reads invalidated, so subsequent file and directory reads re-read the live host instead of a snapshot cached earlier in the session. +func (r *Workspace) Reloaded() *Workspace { + q := r.query.Select("reloaded") + + return &Workspace{ + query: q, + } +} + +// An installed SDK, by name. +func (r *Workspace) SDK(name string) *WorkspaceSDK { + q := r.query.Select("sdk") + q = q.Arg("name", name) + + return &WorkspaceSDK{ + query: q, + } +} + +// Installed SDKs. +func (r *Workspace) Sdks(ctx context.Context) ([]WorkspaceSDK, error) { + q := r.query.Select("sdks") + + q = q.Select("id") + + type sdks struct { + Id ID + } + + convert := func(fields []sdks) []WorkspaceSDK { + out := []WorkspaceSDK{} + + for i := range fields { + val := WorkspaceSDK{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "WorkspaceSDK") + out = append(out, val) + } + + return out + } + var response []sdks + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// WorkspaceSearchOpts contains options for Workspace.Search +type WorkspaceSearchOpts struct { + // Directory or file paths to search + Paths []string + // Glob patterns to match (e.g., "*.md") + Globs []string + // Interpret the pattern as a literal string instead of a regular expression. + Literal bool + // Enable searching across multiple lines. + Multiline bool + // Allow the . pattern to match newlines in multiline mode. + Dotall bool + // Enable case-insensitive matching. + Insensitive bool + // Honor .gitignore, .ignore, and .rgignore files. + SkipIgnored bool + // Skip hidden files (files starting with .). + SkipHidden bool + // Only return matching files, not lines and content + FilesOnly bool + // Limit the number of results to return + Limit int +} + +// Searches for content matching the given regular expression or literal string. +// +// Uses Rust regex syntax; escape literal ., [, ], {, }, | with backslashes. +// +// Runs ripgrep on the client host, falling back to grep if unavailable. +func (r *Workspace) Search(ctx context.Context, pattern string, opts ...WorkspaceSearchOpts) ([]SearchResult, error) { + q := r.query.Select("search") + for i := len(opts) - 1; i >= 0; i-- { + // `paths` optional argument + if !querybuilder.IsZeroValue(opts[i].Paths) { + q = q.Arg("paths", opts[i].Paths) + } + // `globs` optional argument + if !querybuilder.IsZeroValue(opts[i].Globs) { + q = q.Arg("globs", opts[i].Globs) + } + // `literal` optional argument + if !querybuilder.IsZeroValue(opts[i].Literal) { + q = q.Arg("literal", opts[i].Literal) + } + // `multiline` optional argument + if !querybuilder.IsZeroValue(opts[i].Multiline) { + q = q.Arg("multiline", opts[i].Multiline) + } + // `dotall` optional argument + if !querybuilder.IsZeroValue(opts[i].Dotall) { + q = q.Arg("dotall", opts[i].Dotall) + } + // `insensitive` optional argument + if !querybuilder.IsZeroValue(opts[i].Insensitive) { + q = q.Arg("insensitive", opts[i].Insensitive) + } + // `skipIgnored` optional argument + if !querybuilder.IsZeroValue(opts[i].SkipIgnored) { + q = q.Arg("skipIgnored", opts[i].SkipIgnored) + } + // `skipHidden` optional argument + if !querybuilder.IsZeroValue(opts[i].SkipHidden) { + q = q.Arg("skipHidden", opts[i].SkipHidden) + } + // `filesOnly` optional argument + if !querybuilder.IsZeroValue(opts[i].FilesOnly) { + q = q.Arg("filesOnly", opts[i].FilesOnly) + } + // `limit` optional argument + if !querybuilder.IsZeroValue(opts[i].Limit) { + q = q.Arg("limit", opts[i].Limit) + } + } + q = q.Arg("pattern", pattern) + + q = q.Select("id") + + type search struct { + Id ID + } + + convert := func(fields []search) []SearchResult { + out := []SearchResult{} + + for i := range fields { + val := SearchResult{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "SearchResult") + out = append(out, val) + } + + return out + } + var response []search + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// WorkspaceServicesOpts contains options for Workspace.Services +type WorkspaceServicesOpts struct { + // Only include services matching the specified patterns + Include []string +} + +// Return all services from modules loaded in the workspace. +func (r *Workspace) Services(opts ...WorkspaceServicesOpts) *UpGroup { + q := r.query.Select("services") + for i := len(opts) - 1; i >= 0; i-- { + // `include` optional argument + if !querybuilder.IsZeroValue(opts[i].Include) { + q = q.Arg("include", opts[i].Include) + } + } + + return &UpGroup{ + query: q, + } +} + +// Return this workspace with a changeset applied, without mutating the source. +func (r *Workspace) WithChanges(changes *Changeset) *Workspace { + assertNotNil("changes", changes) + q := r.query.Select("withChanges") + q = q.Arg("changes", changes) + + return &Workspace{ + query: q, + } +} + +// WorkspaceWithConfigEnvOpts contains options for Workspace.WithConfigEnv +type WorkspaceWithConfigEnvOpts struct { + // Write to the workspace config directory at the workspace cwd. + Here bool +} + +// Return this workspace with a named config environment created. +func (r *Workspace) WithConfigEnv(name string, opts ...WorkspaceWithConfigEnvOpts) *Workspace { + q := r.query.Select("withConfigEnv") + for i := len(opts) - 1; i >= 0; i-- { + // `here` optional argument + if !querybuilder.IsZeroValue(opts[i].Here) { + q = q.Arg("here", opts[i].Here) + } + } + q = q.Arg("name", name) + + return &Workspace{ + query: q, + } +} + +// WorkspaceWithConfigValueOpts contains options for Workspace.WithConfigValue +type WorkspaceWithConfigValueOpts struct { + // List value to set. Elements are stored verbatim, with no auto-detection. Mutually exclusive with value. + Values []string + // Write to the workspace config directory at the workspace cwd. + Here bool +} + +// Return this workspace with a configuration value written. +// +// When the session selects an env, the key is scoped to that env's overlay and the env is created if missing. +func (r *Workspace) WithConfigValue(key string, value string, opts ...WorkspaceWithConfigValueOpts) *Workspace { + q := r.query.Select("withConfigValue") + for i := len(opts) - 1; i >= 0; i-- { + // `values` optional argument + if !querybuilder.IsZeroValue(opts[i].Values) { + q = q.Arg("values", opts[i].Values) + } + // `here` optional argument + if !querybuilder.IsZeroValue(opts[i].Here) { + q = q.Arg("here", opts[i].Here) + } + } + q = q.Arg("key", key) + q = q.Arg("value", value) + + return &Workspace{ + query: q, + } +} + +// WorkspaceWithInitClientOpts contains options for Workspace.WithInitClient +type WorkspaceWithInitClientOpts struct { + // SDK-specific init arguments. + Args JSON + // Write to the workspace config directory at the workspace cwd. + Here bool + // Skip running the SDK's generators for the new client. + NoGenerate bool +} + +// Return this workspace with a generated API client initialized. +// +// The SDK's generators run for the new client, so the returned workspace carries its generated bindings. +func (r *Workspace) WithInitClient(path string, sdk string, module string, opts ...WorkspaceWithInitClientOpts) *Workspace { + q := r.query.Select("withInitClient") + for i := len(opts) - 1; i >= 0; i-- { + // `args` optional argument + if !querybuilder.IsZeroValue(opts[i].Args) { + q = q.Arg("args", opts[i].Args) + } + // `here` optional argument + if !querybuilder.IsZeroValue(opts[i].Here) { + q = q.Arg("here", opts[i].Here) + } + // `noGenerate` optional argument + if !querybuilder.IsZeroValue(opts[i].NoGenerate) { + q = q.Arg("noGenerate", opts[i].NoGenerate) + } + } + q = q.Arg("path", path) + q = q.Arg("sdk", sdk) + q = q.Arg("module", module) + + return &Workspace{ + query: q, + } +} + +// WorkspaceWithInitModuleOpts contains options for Workspace.WithInitModule +type WorkspaceWithInitModuleOpts struct { + // Path for the new module, relative to the workspace cwd; a leading "/" is relative to the workspace root. Defaults to .dagger/modules/ beside the workspace config. + Path string + // Source subpath within the new module. + Source string + // Additional include patterns for the module. + Include []string + // SDK-specific init arguments. + Args JSON + // Write to the workspace config directory at the workspace cwd. + Here bool + // Skip running the SDK's generators for the new module. + NoGenerate bool +} + +// Return this workspace with a new module initialized. +// +// The SDK's generators run for the new module, so the returned workspace carries the generated code it needs to be loadable. +func (r *Workspace) WithInitModule(name string, sdk string, opts ...WorkspaceWithInitModuleOpts) *Workspace { + q := r.query.Select("withInitModule") + for i := len(opts) - 1; i >= 0; i-- { + // `path` optional argument + if !querybuilder.IsZeroValue(opts[i].Path) { + q = q.Arg("path", opts[i].Path) + } + // `source` optional argument + if !querybuilder.IsZeroValue(opts[i].Source) { + q = q.Arg("source", opts[i].Source) + } + // `include` optional argument + if !querybuilder.IsZeroValue(opts[i].Include) { + q = q.Arg("include", opts[i].Include) + } + // `args` optional argument + if !querybuilder.IsZeroValue(opts[i].Args) { + q = q.Arg("args", opts[i].Args) + } + // `here` optional argument + if !querybuilder.IsZeroValue(opts[i].Here) { + q = q.Arg("here", opts[i].Here) + } + // `noGenerate` optional argument + if !querybuilder.IsZeroValue(opts[i].NoGenerate) { + q = q.Arg("noGenerate", opts[i].NoGenerate) + } + } + q = q.Arg("name", name) + q = q.Arg("sdk", sdk) + + return &Workspace{ + query: q, + } +} + +// WorkspaceWithModuleOpts contains options for Workspace.WithModule +type WorkspaceWithModuleOpts struct { + // Override name for the installed module entry. + Name string + // Write to the workspace config directory at the workspace cwd. + Here bool +} + +// Return this workspace with a module installed in its config. +// +// When the session selects an env, the module is recorded in that env's overlay and the env is created if missing. +func (r *Workspace) WithModule(ref string, opts ...WorkspaceWithModuleOpts) *Workspace { + q := r.query.Select("withModule") + for i := len(opts) - 1; i >= 0; i-- { + // `name` optional argument + if !querybuilder.IsZeroValue(opts[i].Name) { + q = q.Arg("name", opts[i].Name) + } + // `here` optional argument + if !querybuilder.IsZeroValue(opts[i].Here) { + q = q.Arg("here", opts[i].Here) + } + } + q = q.Arg("ref", ref) + + return &Workspace{ + query: q, + } +} + +// Return this workspace with a directory mounted read-only at the given path, without mutating the source. +// +// Mounted content is readable through the normal workspace file tools but shadows the source at the mount path and stays out of the pending changeset: it never appears in changes, is never exported, and cannot be modified. +func (r *Workspace) WithMountedDirectory(path string, source *Directory) *Workspace { + assertNotNil("source", source) + q := r.query.Select("withMountedDirectory") + q = q.Arg("path", path) + q = q.Arg("source", source) + + return &Workspace{ + query: q, + } +} + +// Return this workspace with a file mounted read-only at the given path, without mutating the source. +// +// Mounted content is readable through the normal workspace file tools but shadows the source at the mount path and stays out of the pending changeset: it never appears in changes, is never exported, and cannot be modified. +func (r *Workspace) WithMountedFile(path string, source *File) *Workspace { + assertNotNil("source", source) + q := r.query.Select("withMountedFile") + q = q.Arg("path", path) + q = q.Arg("source", source) + + return &Workspace{ + query: q, + } +} + +// Return this workspace with a directory added, without mutating the source. +func (r *Workspace) WithNewDirectory(path string, source *Directory) *Workspace { + assertNotNil("source", source) + q := r.query.Select("withNewDirectory") + q = q.Arg("path", path) + q = q.Arg("source", source) + + return &Workspace{ + query: q, + } +} + +// WorkspaceWithNewFileOpts contains options for Workspace.WithNewFile +type WorkspaceWithNewFileOpts struct { + // Permissions of the new file. + // + // Default: 420 + Permissions int +} + +// Return this workspace with a new or replaced file, without mutating the source. +func (r *Workspace) WithNewFile(path string, contents string, opts ...WorkspaceWithNewFileOpts) *Workspace { + q := r.query.Select("withNewFile") + for i := len(opts) - 1; i >= 0; i-- { + // `permissions` optional argument + if !querybuilder.IsZeroValue(opts[i].Permissions) { + q = q.Arg("permissions", opts[i].Permissions) + } + } + q = q.Arg("path", path) + q = q.Arg("contents", contents) + + return &Workspace{ + query: q, + } +} + +// WorkspaceWithSDKOpts contains options for Workspace.WithSDK +type WorkspaceWithSDKOpts struct { + // Override name for the installed SDK entry. + Name string + // Write to the workspace config directory at the workspace cwd. + Here bool + // User-facing SDK name to persist under `[modules..as-sdk] name = ...`. + AsSDKName string +} + +// Return this workspace with an SDK installed in its config. +func (r *Workspace) WithSDK(ref string, opts ...WorkspaceWithSDKOpts) *Workspace { + q := r.query.Select("withSDK") + for i := len(opts) - 1; i >= 0; i-- { + // `name` optional argument + if !querybuilder.IsZeroValue(opts[i].Name) { + q = q.Arg("name", opts[i].Name) + } + // `here` optional argument + if !querybuilder.IsZeroValue(opts[i].Here) { + q = q.Arg("here", opts[i].Here) + } + // `asSdkName` optional argument + if !querybuilder.IsZeroValue(opts[i].AsSDKName) { + q = q.Arg("asSdkName", opts[i].AsSDKName) + } + } + q = q.Arg("ref", ref) + + return &Workspace{ + query: q, + } +} + +// Return this workspace with refreshed lockfile state. +func (r *Workspace) WithUpdatedLock() *Workspace { + q := r.query.Select("withUpdatedLock") + + return &Workspace{ + query: q, + } +} + +// Return this workspace with its working directory pointed at the given workspace-relative path. +func (r *Workspace) WithWorkdir(path string) *Workspace { + q := r.query.Select("withWorkdir") + q = q.Arg("path", path) + + return &Workspace{ + query: q, + } +} + +// WorkspaceWithoutConfigEnvOpts contains options for Workspace.WithoutConfigEnv +type WorkspaceWithoutConfigEnvOpts struct { + // Write to the workspace config directory at the workspace cwd. + Here bool +} + +// Return this workspace with a named config environment removed. +func (r *Workspace) WithoutConfigEnv(name string, opts ...WorkspaceWithoutConfigEnvOpts) *Workspace { + q := r.query.Select("withoutConfigEnv") + for i := len(opts) - 1; i >= 0; i-- { + // `here` optional argument + if !querybuilder.IsZeroValue(opts[i].Here) { + q = q.Arg("here", opts[i].Here) + } + } + q = q.Arg("name", name) + + return &Workspace{ + query: q, + } +} + +// WorkspaceWithoutConfigValueOpts contains options for Workspace.WithoutConfigValue +type WorkspaceWithoutConfigValueOpts struct { + // Write to the workspace config directory at the workspace cwd. + Here bool +} + +// Return this workspace with a configuration value removed. +// +// Errors when the key is not currently set. +// +// When the session selects an env, the key is scoped to that env's overlay. +func (r *Workspace) WithoutConfigValue(key string, opts ...WorkspaceWithoutConfigValueOpts) *Workspace { + q := r.query.Select("withoutConfigValue") + for i := len(opts) - 1; i >= 0; i-- { + // `here` optional argument + if !querybuilder.IsZeroValue(opts[i].Here) { + q = q.Arg("here", opts[i].Here) + } + } + q = q.Arg("key", key) + + return &Workspace{ + query: q, + } +} + +// Return this workspace with a directory removed, without mutating the source. +func (r *Workspace) WithoutDirectory(path string) *Workspace { + q := r.query.Select("withoutDirectory") + q = q.Arg("path", path) + + return &Workspace{ + query: q, + } +} + +// Return this workspace with a file removed, without mutating the source. +func (r *Workspace) WithoutFile(path string) *Workspace { + q := r.query.Select("withoutFile") + q = q.Arg("path", path) + + return &Workspace{ + query: q, + } +} + +// WorkspaceWithoutModuleOpts contains options for Workspace.WithoutModule +type WorkspaceWithoutModuleOpts struct { + // Write to the workspace config directory at the workspace cwd. + Here bool +} + +// Return this workspace with a module removed from its config. +// +// When the session selects an env, only that env's overlay entry is removed. +func (r *Workspace) WithoutModule(name string, opts ...WorkspaceWithoutModuleOpts) *Workspace { + q := r.query.Select("withoutModule") + for i := len(opts) - 1; i >= 0; i-- { + // `here` optional argument + if !querybuilder.IsZeroValue(opts[i].Here) { + q = q.Arg("here", opts[i].Here) + } + } + q = q.Arg("name", name) + + return &Workspace{ + query: q, + } +} + +// WorkspaceWithoutSDKOpts contains options for Workspace.WithoutSDK +type WorkspaceWithoutSDKOpts struct { + // Write to the workspace config directory at the workspace cwd. + Here bool +} + +// Return this workspace with an SDK removed from its config. +func (r *Workspace) WithoutSDK(name string, opts ...WorkspaceWithoutSDKOpts) *Workspace { + q := r.query.Select("withoutSDK") + for i := len(opts) - 1; i >= 0; i-- { + // `here` optional argument + if !querybuilder.IsZeroValue(opts[i].Here) { + q = q.Arg("here", opts[i].Here) + } + } + q = q.Arg("name", name) + + return &Workspace{ + query: q, + } +} + +// AsNode returns this Workspace as a Node. +// This is a local type conversion — no GraphQL call. +func (r *Workspace) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// Local git state for a workspace. +type WorkspaceGit struct { + query *querybuilder.Selection + + id *ID +} + +func (r *WorkspaceGit) WithGraphQLQuery(q *querybuilder.Selection) *WorkspaceGit { + return &WorkspaceGit{ + query: q, + } +} + +// The checked-out HEAD of this workspace. +func (r *WorkspaceGit) Head() *GitRef { + q := r.query.Select("head") + + return &GitRef{ + query: q, + } +} + +// A unique identifier for this WorkspaceGit. +func (r *WorkspaceGit) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *WorkspaceGit) XXX_GraphQLType() string { + return "WorkspaceGit" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *WorkspaceGit) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *WorkspaceGit) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *WorkspaceGit) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *WorkspaceGit) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = WorkspaceGit{query: selectNode(dag.query, id, "WorkspaceGit")} + return nil +} + +// Uncommitted changes in this workspace, using the same rules as GitRepository.uncommitted. +func (r *WorkspaceGit) Uncommitted() *Changeset { + q := r.query.Select("uncommitted") + + return &Changeset{ + query: q, + } +} + +// AsNode returns this WorkspaceGit as a Node. +// This is a local type conversion — no GraphQL call. +func (r *WorkspaceGit) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A planned workspace migration. +type WorkspaceMigration struct { + query *querybuilder.Selection + + id *ID +} + +func (r *WorkspaceMigration) WithGraphQLQuery(q *querybuilder.Selection) *WorkspaceMigration { + return &WorkspaceMigration{ + query: q, + } +} + +// Filesystem changes for the full migration plan. +func (r *WorkspaceMigration) Changes() *Changeset { + q := r.query.Select("changes") + + return &Changeset{ + query: q, + } +} + +// A unique identifier for this WorkspaceMigration. +func (r *WorkspaceMigration) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *WorkspaceMigration) XXX_GraphQLType() string { + return "WorkspaceMigration" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *WorkspaceMigration) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *WorkspaceMigration) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *WorkspaceMigration) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *WorkspaceMigration) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = WorkspaceMigration{query: selectNode(dag.query, id, "WorkspaceMigration")} + return nil +} + +// Logical migration steps, each identified by a stable code. +func (r *WorkspaceMigration) Steps(ctx context.Context) ([]WorkspaceMigrationStep, error) { + q := r.query.Select("steps") + + q = q.Select("id") + + type steps struct { + Id ID + } + + convert := func(fields []steps) []WorkspaceMigrationStep { + out := []WorkspaceMigrationStep{} + + for i := range fields { + val := WorkspaceMigrationStep{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "WorkspaceMigrationStep") + out = append(out, val) + } + + return out + } + var response []steps + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// AsNode returns this WorkspaceMigration as a Node. +// This is a local type conversion — no GraphQL call. +func (r *WorkspaceMigration) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A single logical part of a workspace migration. +type WorkspaceMigrationStep struct { + query *querybuilder.Selection + + code *string + description *string + id *ID +} + +func (r *WorkspaceMigrationStep) WithGraphQLQuery(q *querybuilder.Selection) *WorkspaceMigrationStep { + return &WorkspaceMigrationStep{ + query: q, + } +} + +// Filesystem changes for this step. +func (r *WorkspaceMigrationStep) Changes() *Changeset { + q := r.query.Select("changes") + + return &Changeset{ + query: q, + } +} + +// Stable code identifying this logical migration step. +func (r *WorkspaceMigrationStep) Code(ctx context.Context) (string, error) { + if r.code != nil { + return *r.code, nil + } + q := r.query.Select("code") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// Generic summary of this step's purpose and impact. +func (r *WorkspaceMigrationStep) Description(ctx context.Context) (string, error) { + if r.description != nil { + return *r.description, nil + } + q := r.query.Select("description") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this WorkspaceMigrationStep. +func (r *WorkspaceMigrationStep) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *WorkspaceMigrationStep) XXX_GraphQLType() string { + return "WorkspaceMigrationStep" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *WorkspaceMigrationStep) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *WorkspaceMigrationStep) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *WorkspaceMigrationStep) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *WorkspaceMigrationStep) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = WorkspaceMigrationStep{query: selectNode(dag.query, id, "WorkspaceMigrationStep")} + return nil +} + +// Non-fatal warnings raised while planning this step. +func (r *WorkspaceMigrationStep) Warnings(ctx context.Context) ([]string, error) { + q := r.query.Select("warnings") + + var response []string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this WorkspaceMigrationStep as a Node. +// This is a local type conversion — no GraphQL call. +func (r *WorkspaceMigrationStep) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A module entry in the workspace configuration. +type WorkspaceModule struct { + query *querybuilder.Selection + + entrypoint *bool + id *ID + name *string + source *string +} + +func (r *WorkspaceModule) WithGraphQLQuery(q *querybuilder.Selection) *WorkspaceModule { + return &WorkspaceModule{ + query: q, + } +} + +// Whether the module is the workspace entrypoint (functions aliased to Query root). +func (r *WorkspaceModule) Entrypoint(ctx context.Context) (bool, error) { + if r.entrypoint != nil { + return *r.entrypoint, nil + } + q := r.query.Select("entrypoint") + + var response bool + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this WorkspaceModule. +func (r *WorkspaceModule) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *WorkspaceModule) XXX_GraphQLType() string { + return "WorkspaceModule" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *WorkspaceModule) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *WorkspaceModule) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *WorkspaceModule) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *WorkspaceModule) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = WorkspaceModule{query: selectNode(dag.query, id, "WorkspaceModule")} + return nil +} + +// The module name. +func (r *WorkspaceModule) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// List constructor-backed settings for this module. +func (r *WorkspaceModule) Settings(ctx context.Context) ([]WorkspaceModuleSetting, error) { + q := r.query.Select("settings") + + q = q.Select("id") + + type settings struct { + Id ID + } + + convert := func(fields []settings) []WorkspaceModuleSetting { + out := []WorkspaceModuleSetting{} + + for i := range fields { + val := WorkspaceModuleSetting{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "WorkspaceModuleSetting") + out = append(out, val) + } + + return out + } + var response []settings + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// The module source path. +func (r *WorkspaceModule) Source(ctx context.Context) (string, error) { + if r.source != nil { + return *r.source, nil + } + q := r.query.Select("source") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this WorkspaceModule as a Node. +// This is a local type conversion — no GraphQL call. +func (r *WorkspaceModule) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// A constructor-backed module setting. +type WorkspaceModuleSetting struct { + query *querybuilder.Selection + + description *string + id *ID + isList *bool + key *string + value *string +} + +func (r *WorkspaceModuleSetting) WithGraphQLQuery(q *querybuilder.Selection) *WorkspaceModuleSetting { + return &WorkspaceModuleSetting{ + query: q, + } +} + +// The constructor argument description. +func (r *WorkspaceModuleSetting) Description(ctx context.Context) (string, error) { + if r.description != nil { + return *r.description, nil + } + q := r.query.Select("description") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// A unique identifier for this WorkspaceModuleSetting. +func (r *WorkspaceModuleSetting) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *WorkspaceModuleSetting) XXX_GraphQLType() string { + return "WorkspaceModuleSetting" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *WorkspaceModuleSetting) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *WorkspaceModuleSetting) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *WorkspaceModuleSetting) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *WorkspaceModuleSetting) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = WorkspaceModuleSetting{query: selectNode(dag.query, id, "WorkspaceModuleSetting")} + return nil +} + +// Whether the setting accepts a list of values. +func (r *WorkspaceModuleSetting) IsList(ctx context.Context) (bool, error) { + if r.isList != nil { + return *r.isList, nil + } + q := r.query.Select("isList") + + var response bool + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The setting key. +func (r *WorkspaceModuleSetting) Key(ctx context.Context) (string, error) { + if r.key != nil { + return *r.key, nil + } + q := r.query.Select("key") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The configured value after applying the selected workspace environment, or empty when unset. +func (r *WorkspaceModuleSetting) Value(ctx context.Context) (string, error) { + if r.value != nil { + return *r.value, nil + } + q := r.query.Select("value") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this WorkspaceModuleSetting as a Node. +// This is a local type conversion — no GraphQL call. +func (r *WorkspaceModuleSetting) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// An installed SDK: a module marked for scaffolding other modules and clients. +type WorkspaceSDK struct { + query *querybuilder.Selection + + id *ID + name *string + ref *string +} + +func (r *WorkspaceSDK) WithGraphQLQuery(q *querybuilder.Selection) *WorkspaceSDK { + return &WorkspaceSDK{ + query: q, + } +} + +// Clients generated with this SDK. +func (r *WorkspaceSDK) Clients(ctx context.Context) ([]WorkspaceModule, error) { + q := r.query.Select("clients") + + q = q.Select("id") + + type clients struct { + Id ID + } + + convert := func(fields []clients) []WorkspaceModule { + out := []WorkspaceModule{} + + for i := range fields { + val := WorkspaceModule{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "WorkspaceModule") + out = append(out, val) + } + + return out + } + var response []clients + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// A unique identifier for this WorkspaceSDK. +func (r *WorkspaceSDK) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *WorkspaceSDK) XXX_GraphQLType() string { + return "WorkspaceSDK" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *WorkspaceSDK) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *WorkspaceSDK) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *WorkspaceSDK) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *WorkspaceSDK) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = WorkspaceSDK{query: selectNode(dag.query, id, "WorkspaceSDK")} + return nil +} + +// Modules authored with this SDK. +func (r *WorkspaceSDK) Modules(ctx context.Context) ([]WorkspaceModule, error) { + q := r.query.Select("modules") + + q = q.Select("id") + + type modules struct { + Id ID + } + + convert := func(fields []modules) []WorkspaceModule { + out := []WorkspaceModule{} + + for i := range fields { + val := WorkspaceModule{id: &fields[i].Id} + val.query = selectNode(q.Root(), fields[i].Id, "WorkspaceModule") + out = append(out, val) + } + + return out + } + var response []modules + + q = q.Bind(&response) + + err := q.Execute(ctx) + if err != nil { + return nil, err + } + + return convert(response), nil +} + +// The user-facing SDK name. +func (r *WorkspaceSDK) Name(ctx context.Context) (string, error) { + if r.name != nil { + return *r.name, nil + } + q := r.query.Select("name") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// The module reference this SDK was installed from. +func (r *WorkspaceSDK) Ref(ctx context.Context) (string, error) { + if r.ref != nil { + return *r.ref, nil + } + q := r.query.Select("ref") + + var response string + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// AsNode returns this WorkspaceSDK as a Node. +// This is a local type conversion — no GraphQL call. +func (r *WorkspaceSDK) AsNode() Node { + return &NodeClient{ + query: r.query, + } +} + +// An object that can be exported to the host. +// +// Calling export writes the object to a path on the host filesystem and returns the path that was written. +type Exportable interface { + DaggerObject + + Export(ctx context.Context, path string) (string, error) + + ID(ctx context.Context) (ID, error) + + // Concrete loads and returns the underlying concrete type of this + // interface, which can then be used with a type switch. + Concrete(ctx context.Context) (Node, error) +} + +// ExportableClient is the query-builder for the Exportable interface. +type ExportableClient struct { + query *querybuilder.Selection + + export *string + id *ID +} + +func (r *ExportableClient) WithGraphQLQuery(q *querybuilder.Selection) *ExportableClient { + return &ExportableClient{ + query: q, + } +} + +func (r *ExportableClient) Export(ctx context.Context, path string) (string, error) { + if r.export != nil { + return *r.export, nil + } + q := r.query.Select("export") + q = q.Arg("path", path) + + var response string + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +func (r *ExportableClient) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *ExportableClient) XXX_GraphQLType() string { + return "Exportable" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *ExportableClient) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *ExportableClient) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *ExportableClient) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *ExportableClient) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = ExportableClient{query: selectNode(dag.query, id, "Exportable")} + return nil +} + +// Concrete loads and returns the underlying concrete type of this +// interface, which can then be used with a type switch. +func (r *ExportableClient) Concrete(ctx context.Context) (Node, error) { + // Query __typename to determine the concrete type. + var typeName string + q := r.query.Select("__typename") + q = q.Bind(&typeName) + if err := q.Execute(ctx); err != nil { + return nil, err + } + // Get the ID to load the concrete object. + id, err := r.ID(ctx) + if err != nil { + return nil, err + } + switch typeName { + case "Changeset": + return &Changeset{query: selectNode(r.query.Root(), id, "Changeset")}, nil + case "Container": + return &Container{query: selectNode(r.query.Root(), id, "Container")}, nil + case "Directory": + return &Directory{query: selectNode(r.query.Root(), id, "Directory")}, nil + case "File": + return &File{query: selectNode(r.query.Root(), id, "File")}, nil + default: + return nil, fmt.Errorf("unknown Exportable implementation: %s", typeName) + } +} + +// An object with a globally unique ID. +type Node interface { + DaggerObject + + ID(ctx context.Context) (ID, error) +} + +// NodeClient is the query-builder for the Node interface. +type NodeClient struct { + query *querybuilder.Selection + + id *ID +} + +func (r *NodeClient) WithGraphQLQuery(q *querybuilder.Selection) *NodeClient { + return &NodeClient{ + query: q, + } +} + +func (r *NodeClient) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *NodeClient) XXX_GraphQLType() string { + return "Node" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *NodeClient) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *NodeClient) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *NodeClient) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *NodeClient) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = NodeClient{query: selectNode(dag.query, id, "Node")} + return nil +} + +// An object that can be force-evaluated. +// +// Calling sync ensures that the object's entire dependency DAG has been evaluated, returning the object's ID once complete. +type Syncer interface { + DaggerObject + + ID(ctx context.Context) (ID, error) + + Sync(ctx context.Context) (Syncer, error) + + // Concrete loads and returns the underlying concrete type of this + // interface, which can then be used with a type switch. + Concrete(ctx context.Context) (Node, error) +} + +// SyncerClient is the query-builder for the Syncer interface. +type SyncerClient struct { + query *querybuilder.Selection + + id *ID + sync *ID +} + +func (r *SyncerClient) WithGraphQLQuery(q *querybuilder.Selection) *SyncerClient { + return &SyncerClient{ + query: q, + } +} + +func (r *SyncerClient) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *SyncerClient) XXX_GraphQLType() string { + return "Syncer" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *SyncerClient) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *SyncerClient) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *SyncerClient) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *SyncerClient) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = SyncerClient{query: selectNode(dag.query, id, "Syncer")} + return nil +} + +func (r *SyncerClient) Sync(ctx context.Context) (Syncer, error) { + q := r.query.Select("sync") + + var id ID + if err := q.Bind(&id).Execute(ctx); err != nil { + return nil, err + } + return &SyncerClient{ + query: selectNode(q.Root(), id, "Syncer"), + }, nil +} + +// Concrete loads and returns the underlying concrete type of this +// interface, which can then be used with a type switch. +func (r *SyncerClient) Concrete(ctx context.Context) (Node, error) { + // Query __typename to determine the concrete type. + var typeName string + q := r.query.Select("__typename") + q = q.Bind(&typeName) + if err := q.Execute(ctx); err != nil { + return nil, err + } + // Get the ID to load the concrete object. + id, err := r.ID(ctx) + if err != nil { + return nil, err + } + switch typeName { + case "Changeset": + return &Changeset{query: selectNode(r.query.Root(), id, "Changeset")}, nil + case "Container": + return &Container{query: selectNode(r.query.Root(), id, "Container")}, nil + case "Directory": + return &Directory{query: selectNode(r.query.Root(), id, "Directory")}, nil + case "File": + return &File{query: selectNode(r.query.Root(), id, "File")}, nil + case "LLM": + return &LLM{query: selectNode(r.query.Root(), id, "LLM")}, nil + case "Module": + return &Module{query: selectNode(r.query.Root(), id, "Module")}, nil + case "ModuleSource": + return &ModuleSource{query: selectNode(r.query.Root(), id, "ModuleSource")}, nil + case "Service": + return &Service{query: selectNode(r.query.Root(), id, "Service")}, nil + case "Terminal": + return &Terminal{query: selectNode(r.query.Root(), id, "Terminal")}, nil + default: + return nil, fmt.Errorf("unknown Syncer implementation: %s", typeName) + } +} + +// Sharing mode of the cache volume. +type CacheSharingMode string + +func (CacheSharingMode) IsEnum() {} + +func (v CacheSharingMode) Name() string { + switch v { + case CacheSharingModeShared: + return "SHARED" + case CacheSharingModePrivate: + return "PRIVATE" + case CacheSharingModeLocked: + return "LOCKED" + default: + return "" + } +} + +func (v CacheSharingMode) Value() string { + return string(v) +} + +func (v *CacheSharingMode) MarshalJSON() ([]byte, error) { + if *v == "" { + return []byte(`""`), nil + } + name := v.Name() + if name == "" { + return nil, fmt.Errorf("invalid enum value %q", *v) + } + return json.Marshal(name) +} + +func (v *CacheSharingMode) UnmarshalJSON(dt []byte) error { + var s string + if err := json.Unmarshal(dt, &s); err != nil { + return err + } + switch s { + case "": + *v = "" + case "LOCKED": + *v = CacheSharingModeLocked + case "PRIVATE": + *v = CacheSharingModePrivate + case "SHARED": + *v = CacheSharingModeShared + default: + return fmt.Errorf("invalid enum value %q", s) + } + return nil +} + +const ( + // Shares the cache volume amongst many build pipelines + CacheSharingModeShared CacheSharingMode = "SHARED" + + // Keeps a cache volume for a single build pipeline + CacheSharingModePrivate CacheSharingMode = "PRIVATE" + + // Shares the cache volume amongst many build pipelines, but will serialize the writes + CacheSharingModeLocked CacheSharingMode = "LOCKED" +) + +// Strategy to use when merging changesets with conflicting changes. +type ChangesetMergeConflict string + +func (ChangesetMergeConflict) IsEnum() {} + +func (v ChangesetMergeConflict) Name() string { + switch v { + case ChangesetMergeConflictFailEarly: + return "FAIL_EARLY" + case ChangesetMergeConflictFail: + return "FAIL" + case ChangesetMergeConflictLeaveConflictMarkers: + return "LEAVE_CONFLICT_MARKERS" + case ChangesetMergeConflictPreferOurs: + return "PREFER_OURS" + case ChangesetMergeConflictPreferTheirs: + return "PREFER_THEIRS" + default: + return "" + } +} + +func (v ChangesetMergeConflict) Value() string { + return string(v) +} + +func (v *ChangesetMergeConflict) MarshalJSON() ([]byte, error) { + if *v == "" { + return []byte(`""`), nil + } + name := v.Name() + if name == "" { + return nil, fmt.Errorf("invalid enum value %q", *v) + } + return json.Marshal(name) +} + +func (v *ChangesetMergeConflict) UnmarshalJSON(dt []byte) error { + var s string + if err := json.Unmarshal(dt, &s); err != nil { + return err + } + switch s { + case "": + *v = "" + case "FAIL": + *v = ChangesetMergeConflictFail + case "FAIL_EARLY": + *v = ChangesetMergeConflictFailEarly + case "LEAVE_CONFLICT_MARKERS": + *v = ChangesetMergeConflictLeaveConflictMarkers + case "PREFER_OURS": + *v = ChangesetMergeConflictPreferOurs + case "PREFER_THEIRS": + *v = ChangesetMergeConflictPreferTheirs + default: + return fmt.Errorf("invalid enum value %q", s) + } + return nil +} + +const ( + // Fail before attempting merge if file-level conflicts are detected + ChangesetMergeConflictFailEarly ChangesetMergeConflict = "FAIL_EARLY" + + // Attempt the merge and fail if git merge fails due to conflicts + ChangesetMergeConflictFail ChangesetMergeConflict = "FAIL" + + // Let git create conflict markers in files. For modify/delete conflicts, keeps the modified version. Fails on binary conflicts. + ChangesetMergeConflictLeaveConflictMarkers ChangesetMergeConflict = "LEAVE_CONFLICT_MARKERS" + + // The conflict is resolved by applying the version of the calling changeset + ChangesetMergeConflictPreferOurs ChangesetMergeConflict = "PREFER_OURS" + + // The conflict is resolved by applying the version of the other changeset + ChangesetMergeConflictPreferTheirs ChangesetMergeConflict = "PREFER_THEIRS" +) + +// Strategy to use when merging multiple changesets with git octopus merge. +type ChangesetsMergeConflict string + +func (ChangesetsMergeConflict) IsEnum() {} + +func (v ChangesetsMergeConflict) Name() string { + switch v { + case ChangesetsMergeConflictFailEarly: + return "FAIL_EARLY" + case ChangesetsMergeConflictFail: + return "FAIL" + default: + return "" + } +} + +func (v ChangesetsMergeConflict) Value() string { + return string(v) +} + +func (v *ChangesetsMergeConflict) MarshalJSON() ([]byte, error) { + if *v == "" { + return []byte(`""`), nil + } + name := v.Name() + if name == "" { + return nil, fmt.Errorf("invalid enum value %q", *v) + } + return json.Marshal(name) +} + +func (v *ChangesetsMergeConflict) UnmarshalJSON(dt []byte) error { + var s string + if err := json.Unmarshal(dt, &s); err != nil { + return err + } + switch s { + case "": + *v = "" + case "FAIL": + *v = ChangesetsMergeConflictFail + case "FAIL_EARLY": + *v = ChangesetsMergeConflictFailEarly + default: + return fmt.Errorf("invalid enum value %q", s) + } + return nil +} + +const ( + // Fail before attempting merge if file-level conflicts are detected between any changesets + ChangesetsMergeConflictFailEarly ChangesetsMergeConflict = "FAIL_EARLY" + + // Attempt the octopus merge and fail if git merge fails due to conflicts + ChangesetsMergeConflictFail ChangesetsMergeConflict = "FAIL" +) + +// The type of change for a diff stat entry. +type DiffStatKind string + +func (DiffStatKind) IsEnum() {} + +func (v DiffStatKind) Name() string { + switch v { + case DiffStatKindAdded: + return "ADDED" + case DiffStatKindModified: + return "MODIFIED" + case DiffStatKindRemoved: + return "REMOVED" + case DiffStatKindRenamed: + return "RENAMED" + default: + return "" + } +} + +func (v DiffStatKind) Value() string { + return string(v) +} + +func (v *DiffStatKind) MarshalJSON() ([]byte, error) { + if *v == "" { + return []byte(`""`), nil + } + name := v.Name() + if name == "" { + return nil, fmt.Errorf("invalid enum value %q", *v) + } + return json.Marshal(name) +} + +func (v *DiffStatKind) UnmarshalJSON(dt []byte) error { + var s string + if err := json.Unmarshal(dt, &s); err != nil { + return err + } + switch s { + case "": + *v = "" + case "ADDED": + *v = DiffStatKindAdded + case "MODIFIED": + *v = DiffStatKindModified + case "REMOVED": + *v = DiffStatKindRemoved + case "RENAMED": + *v = DiffStatKindRenamed + default: + return fmt.Errorf("invalid enum value %q", s) + } + return nil +} + +const ( + // A file or directory was added. + DiffStatKindAdded DiffStatKind = "ADDED" + + // A file was modified. + DiffStatKindModified DiffStatKind = "MODIFIED" + + // A file or directory was removed. + DiffStatKindRemoved DiffStatKind = "REMOVED" + + // A file was renamed. + DiffStatKindRenamed DiffStatKind = "RENAMED" +) + +// File type. +type ExistsType string + +func (ExistsType) IsEnum() {} + +func (v ExistsType) Name() string { + switch v { + case ExistsTypeRegularType: + return "REGULAR_TYPE" + case ExistsTypeDirectoryType: + return "DIRECTORY_TYPE" + case ExistsTypeSymlinkType: + return "SYMLINK_TYPE" + default: + return "" + } +} + +func (v ExistsType) Value() string { + return string(v) +} + +func (v *ExistsType) MarshalJSON() ([]byte, error) { + if *v == "" { + return []byte(`""`), nil + } + name := v.Name() + if name == "" { + return nil, fmt.Errorf("invalid enum value %q", *v) + } + return json.Marshal(name) +} + +func (v *ExistsType) UnmarshalJSON(dt []byte) error { + var s string + if err := json.Unmarshal(dt, &s); err != nil { + return err + } + switch s { + case "": + *v = "" + case "DIRECTORY_TYPE": + *v = ExistsTypeDirectoryType + case "REGULAR_TYPE": + *v = ExistsTypeRegularType + case "SYMLINK_TYPE": + *v = ExistsTypeSymlinkType + default: + return fmt.Errorf("invalid enum value %q", s) + } + return nil +} + +const ( + // Tests path is a regular file + ExistsTypeRegularType ExistsType = "REGULAR_TYPE" + + // Tests path is a directory + ExistsTypeDirectoryType ExistsType = "DIRECTORY_TYPE" + + // Tests path is a symlink + ExistsTypeSymlinkType ExistsType = "SYMLINK_TYPE" +) + +// File type. +type FileType string + +func (FileType) IsEnum() {} + +func (v FileType) Name() string { + switch v { + case FileTypeUnknown: + return "UNKNOWN" + case FileTypeRegular: + return "REGULAR" + case FileTypeDirectory: + return "DIRECTORY" + case FileTypeSymlink: + return "SYMLINK" + default: + return "" + } +} + +func (v FileType) Value() string { + return string(v) +} + +func (v *FileType) MarshalJSON() ([]byte, error) { + if *v == "" { + return []byte(`""`), nil + } + name := v.Name() + if name == "" { + return nil, fmt.Errorf("invalid enum value %q", *v) + } + return json.Marshal(name) +} + +func (v *FileType) UnmarshalJSON(dt []byte) error { + var s string + if err := json.Unmarshal(dt, &s); err != nil { + return err + } + switch s { + case "": + *v = "" + case "DIRECTORY": + *v = FileTypeDirectory + case "DIRECTORY_TYPE": + *v = FileTypeDirectoryType + case "REGULAR": + *v = FileTypeRegular + case "REGULAR_TYPE": + *v = FileTypeRegularType + case "SYMLINK": + *v = FileTypeSymlink + case "SYMLINK_TYPE": + *v = FileTypeSymlinkType + case "UNKNOWN": + *v = FileTypeUnknown + default: + return fmt.Errorf("invalid enum value %q", s) + } + return nil +} + +const ( + // unknown file type + FileTypeUnknown FileType = "UNKNOWN" + + // regular file type + FileTypeRegular FileType = "REGULAR" + // regular file type + FileTypeRegularType FileType = FileTypeRegular + + // directory file type + FileTypeDirectory FileType = "DIRECTORY" + // directory file type + FileTypeDirectoryType FileType = FileTypeDirectory + + // symlink file type + FileTypeSymlink FileType = "SYMLINK" + // symlink file type + FileTypeSymlinkType FileType = FileTypeSymlink +) + +// The behavior configured for function result caching. +type FunctionCachePolicy string + +func (FunctionCachePolicy) IsEnum() {} + +func (v FunctionCachePolicy) Name() string { + switch v { + case FunctionCachePolicyDefault: + return "Default" + case FunctionCachePolicyPerSession: + return "PerSession" + case FunctionCachePolicyNever: + return "Never" + default: + return "" + } +} + +func (v FunctionCachePolicy) Value() string { + return string(v) +} + +func (v *FunctionCachePolicy) MarshalJSON() ([]byte, error) { + if *v == "" { + return []byte(`""`), nil + } + name := v.Name() + if name == "" { + return nil, fmt.Errorf("invalid enum value %q", *v) + } + return json.Marshal(name) +} + +func (v *FunctionCachePolicy) UnmarshalJSON(dt []byte) error { + var s string + if err := json.Unmarshal(dt, &s); err != nil { + return err + } + switch s { + case "": + *v = "" + case "Default": + *v = FunctionCachePolicyDefault + case "Never": + *v = FunctionCachePolicyNever + case "PerSession": + *v = FunctionCachePolicyPerSession + default: + return fmt.Errorf("invalid enum value %q", s) + } + return nil +} + +const ( + FunctionCachePolicyDefault FunctionCachePolicy = "Default" + + FunctionCachePolicyPerSession FunctionCachePolicy = "PerSession" + + FunctionCachePolicyNever FunctionCachePolicy = "Never" +) + +// Compression algorithm to use for image layers. +type ImageLayerCompression string + +func (ImageLayerCompression) IsEnum() {} + +func (v ImageLayerCompression) Name() string { + switch v { + case ImageLayerCompressionGzip: + return "Gzip" + case ImageLayerCompressionZstd: + return "Zstd" + case ImageLayerCompressionEstarGz: + return "EStarGZ" + case ImageLayerCompressionUncompressed: + return "Uncompressed" + default: + return "" + } +} + +func (v ImageLayerCompression) Value() string { + return string(v) +} + +func (v *ImageLayerCompression) MarshalJSON() ([]byte, error) { + if *v == "" { + return []byte(`""`), nil + } + name := v.Name() + if name == "" { + return nil, fmt.Errorf("invalid enum value %q", *v) + } + return json.Marshal(name) +} + +func (v *ImageLayerCompression) UnmarshalJSON(dt []byte) error { + var s string + if err := json.Unmarshal(dt, &s); err != nil { + return err + } + switch s { + case "": + *v = "" + case "EStarGZ": + *v = ImageLayerCompressionEstarGz + case "ESTARGZ": + *v = ImageLayerCompressionEstargz + case "Gzip": + *v = ImageLayerCompressionGzip + case "Uncompressed": + *v = ImageLayerCompressionUncompressed + case "Zstd": + *v = ImageLayerCompressionZstd + default: + return fmt.Errorf("invalid enum value %q", s) + } + return nil +} + +const ( + ImageLayerCompressionGzip ImageLayerCompression = "Gzip" + + ImageLayerCompressionZstd ImageLayerCompression = "Zstd" + + ImageLayerCompressionEstarGz ImageLayerCompression = "EStarGZ" + ImageLayerCompressionEstargz ImageLayerCompression = ImageLayerCompressionEstarGz + + ImageLayerCompressionUncompressed ImageLayerCompression = "Uncompressed" +) + +// Mediatypes to use in published or exported image metadata. +type ImageMediaTypes string + +func (ImageMediaTypes) IsEnum() {} + +func (v ImageMediaTypes) Name() string { + switch v { + case ImageMediaTypesOcimediaTypes: + return "OCIMediaTypes" + case ImageMediaTypesDockerMediaTypes: + return "DockerMediaTypes" + default: + return "" + } +} + +func (v ImageMediaTypes) Value() string { + return string(v) +} + +func (v *ImageMediaTypes) MarshalJSON() ([]byte, error) { + if *v == "" { + return []byte(`""`), nil + } + name := v.Name() + if name == "" { + return nil, fmt.Errorf("invalid enum value %q", *v) + } + return json.Marshal(name) +} + +func (v *ImageMediaTypes) UnmarshalJSON(dt []byte) error { + var s string + if err := json.Unmarshal(dt, &s); err != nil { + return err + } + switch s { + case "": + *v = "" + case "DOCKER": + *v = ImageMediaTypesDocker + case "DockerMediaTypes": + *v = ImageMediaTypesDockerMediaTypes + case "OCI": + *v = ImageMediaTypesOci + case "OCIMediaTypes": + *v = ImageMediaTypesOcimediaTypes + default: + return fmt.Errorf("invalid enum value %q", s) + } + return nil +} + +const ( + ImageMediaTypesOcimediaTypes ImageMediaTypes = "OCIMediaTypes" + ImageMediaTypesOci ImageMediaTypes = ImageMediaTypesOcimediaTypes + + ImageMediaTypesDockerMediaTypes ImageMediaTypes = "DockerMediaTypes" + ImageMediaTypesDocker ImageMediaTypes = ImageMediaTypesDockerMediaTypes +) + +// The kind of content in a message block. +type LLMContentBlockKind string + +func (LLMContentBlockKind) IsEnum() {} + +func (v LLMContentBlockKind) Name() string { + switch v { + case LLMContentBlockKindText: + return "TEXT" + case LLMContentBlockKindThinking: + return "THINKING" + case LLMContentBlockKindToolCall: + return "TOOL_CALL" + case LLMContentBlockKindToolResult: + return "TOOL_RESULT" + default: + return "" + } +} + +func (v LLMContentBlockKind) Value() string { + return string(v) +} + +func (v *LLMContentBlockKind) MarshalJSON() ([]byte, error) { + if *v == "" { + return []byte(`""`), nil + } + name := v.Name() + if name == "" { + return nil, fmt.Errorf("invalid enum value %q", *v) + } + return json.Marshal(name) +} + +func (v *LLMContentBlockKind) UnmarshalJSON(dt []byte) error { + var s string + if err := json.Unmarshal(dt, &s); err != nil { + return err + } + switch s { + case "": + *v = "" + case "TEXT": + *v = LLMContentBlockKindText + case "THINKING": + *v = LLMContentBlockKindThinking + case "TOOL_CALL": + *v = LLMContentBlockKindToolCall + case "TOOL_RESULT": + *v = LLMContentBlockKindToolResult + default: + return fmt.Errorf("invalid enum value %q", s) + } + return nil +} + +const ( + // Plain text content. + LLMContentBlockKindText LLMContentBlockKind = "TEXT" + + // Model thinking/reasoning content (e.g. Anthropic extended thinking). + LLMContentBlockKindThinking LLMContentBlockKind = "THINKING" + + // A tool/function call from the model. + LLMContentBlockKindToolCall LLMContentBlockKind = "TOOL_CALL" + + // A tool/function result. + LLMContentBlockKindToolResult LLMContentBlockKind = "TOOL_RESULT" +) + +// The role that generated a message. +type LLMMessageRole string + +func (LLMMessageRole) IsEnum() {} + +func (v LLMMessageRole) Name() string { + switch v { + case LLMMessageRoleUser: + return "USER" + case LLMMessageRoleAssistant: + return "ASSISTANT" + case LLMMessageRoleSystem: + return "SYSTEM" + default: + return "" + } +} + +func (v LLMMessageRole) Value() string { + return string(v) +} + +func (v *LLMMessageRole) MarshalJSON() ([]byte, error) { + if *v == "" { + return []byte(`""`), nil + } + name := v.Name() + if name == "" { + return nil, fmt.Errorf("invalid enum value %q", *v) + } + return json.Marshal(name) +} + +func (v *LLMMessageRole) UnmarshalJSON(dt []byte) error { + var s string + if err := json.Unmarshal(dt, &s); err != nil { + return err + } + switch s { + case "": + *v = "" + case "ASSISTANT": + *v = LLMMessageRoleAssistant + case "SYSTEM": + *v = LLMMessageRoleSystem + case "USER": + *v = LLMMessageRoleUser + default: + return fmt.Errorf("invalid enum value %q", s) + } + return nil +} + +const ( + // A user prompt or tool response. + LLMMessageRoleUser LLMMessageRole = "USER" + + // A reply from the model. + LLMMessageRoleAssistant LLMMessageRole = "ASSISTANT" + + // A system prompt. + LLMMessageRoleSystem LLMMessageRole = "SYSTEM" +) + +// Experimental features of a module +type ModuleSourceExperimentalFeature string + +func (ModuleSourceExperimentalFeature) IsEnum() {} + +func (v ModuleSourceExperimentalFeature) Name() string { + switch v { + case ModuleSourceExperimentalFeatureSelfCalls: + return "SELF_CALLS" + default: + return "" + } +} + +func (v ModuleSourceExperimentalFeature) Value() string { + return string(v) +} + +func (v *ModuleSourceExperimentalFeature) MarshalJSON() ([]byte, error) { + if *v == "" { + return []byte(`""`), nil + } + name := v.Name() + if name == "" { + return nil, fmt.Errorf("invalid enum value %q", *v) + } + return json.Marshal(name) +} + +func (v *ModuleSourceExperimentalFeature) UnmarshalJSON(dt []byte) error { + var s string + if err := json.Unmarshal(dt, &s); err != nil { + return err + } + switch s { + case "": + *v = "" + case "SELF_CALLS": + *v = ModuleSourceExperimentalFeatureSelfCalls + default: + return fmt.Errorf("invalid enum value %q", s) + } + return nil +} + +const ( + // Self calls + ModuleSourceExperimentalFeatureSelfCalls ModuleSourceExperimentalFeature = "SELF_CALLS" +) + +// The kind of module source. +type ModuleSourceKind string + +func (ModuleSourceKind) IsEnum() {} + +func (v ModuleSourceKind) Name() string { + switch v { + case ModuleSourceKindLocalSource: + return "LOCAL_SOURCE" + case ModuleSourceKindGitSource: + return "GIT_SOURCE" + case ModuleSourceKindDirSource: + return "DIR_SOURCE" + default: + return "" + } +} + +func (v ModuleSourceKind) Value() string { + return string(v) +} + +func (v *ModuleSourceKind) MarshalJSON() ([]byte, error) { + if *v == "" { + return []byte(`""`), nil + } + name := v.Name() + if name == "" { + return nil, fmt.Errorf("invalid enum value %q", *v) + } + return json.Marshal(name) +} + +func (v *ModuleSourceKind) UnmarshalJSON(dt []byte) error { + var s string + if err := json.Unmarshal(dt, &s); err != nil { + return err + } + switch s { + case "": + *v = "" + case "DIR": + *v = ModuleSourceKindDir + case "DIR_SOURCE": + *v = ModuleSourceKindDirSource + case "GIT": + *v = ModuleSourceKindGit + case "GIT_SOURCE": + *v = ModuleSourceKindGitSource + case "LOCAL": + *v = ModuleSourceKindLocal + case "LOCAL_SOURCE": + *v = ModuleSourceKindLocalSource + default: + return fmt.Errorf("invalid enum value %q", s) + } + return nil +} + +const ( + ModuleSourceKindLocalSource ModuleSourceKind = "LOCAL_SOURCE" + ModuleSourceKindLocal ModuleSourceKind = ModuleSourceKindLocalSource + + ModuleSourceKindGitSource ModuleSourceKind = "GIT_SOURCE" + ModuleSourceKindGit ModuleSourceKind = ModuleSourceKindGitSource + + ModuleSourceKindDirSource ModuleSourceKind = "DIR_SOURCE" + ModuleSourceKindDir ModuleSourceKind = ModuleSourceKindDirSource +) + +// Transport layer network protocol associated to a port. +type NetworkProtocol string + +func (NetworkProtocol) IsEnum() {} + +func (v NetworkProtocol) Name() string { + switch v { + case NetworkProtocolTcp: + return "TCP" + case NetworkProtocolUdp: + return "UDP" + default: + return "" + } +} + +func (v NetworkProtocol) Value() string { + return string(v) +} + +func (v *NetworkProtocol) MarshalJSON() ([]byte, error) { + if *v == "" { + return []byte(`""`), nil + } + name := v.Name() + if name == "" { + return nil, fmt.Errorf("invalid enum value %q", *v) + } + return json.Marshal(name) +} + +func (v *NetworkProtocol) UnmarshalJSON(dt []byte) error { + var s string + if err := json.Unmarshal(dt, &s); err != nil { + return err + } + switch s { + case "": + *v = "" + case "TCP": + *v = NetworkProtocolTcp + case "UDP": + *v = NetworkProtocolUdp + default: + return fmt.Errorf("invalid enum value %q", s) + } + return nil +} + +const ( + NetworkProtocolTcp NetworkProtocol = "TCP" + + NetworkProtocolUdp NetworkProtocol = "UDP" +) + +// How to handle patch hunks that no longer apply to the target content. +type PatchConflict string + +func (PatchConflict) IsEnum() {} + +func (v PatchConflict) Name() string { + switch v { + case PatchConflictFail: + return "FAIL" + case PatchConflictLeaveConflictMarkers: + return "LEAVE_CONFLICT_MARKERS" + default: + return "" + } +} + +func (v PatchConflict) Value() string { + return string(v) +} + +func (v *PatchConflict) MarshalJSON() ([]byte, error) { + if *v == "" { + return []byte(`""`), nil + } + name := v.Name() + if name == "" { + return nil, fmt.Errorf("invalid enum value %q", *v) + } + return json.Marshal(name) +} + +func (v *PatchConflict) UnmarshalJSON(dt []byte) error { + var s string + if err := json.Unmarshal(dt, &s); err != nil { + return err + } + switch s { + case "": + *v = "" + case "FAIL": + *v = PatchConflictFail + case "LEAVE_CONFLICT_MARKERS": + *v = PatchConflictLeaveConflictMarkers + default: + return fmt.Errorf("invalid enum value %q", s) + } + return nil +} + +const ( + // Fail the operation if any part of the patch does not apply. + PatchConflictFail PatchConflict = "FAIL" + + // Apply the hunks that fit and insert conflict markers where hunks no longer match, instead of failing. + PatchConflictLeaveConflictMarkers PatchConflict = "LEAVE_CONFLICT_MARKERS" +) + +// Transport protocol to use for registry operations. +type RegistryProtocol string + +func (RegistryProtocol) IsEnum() {} + +func (v RegistryProtocol) Name() string { + switch v { + case RegistryProtocolHttps: + return "HTTPS" + case RegistryProtocolHttp: + return "HTTP" + default: + return "" + } +} + +func (v RegistryProtocol) Value() string { + return string(v) +} + +func (v *RegistryProtocol) MarshalJSON() ([]byte, error) { + if *v == "" { + return []byte(`""`), nil + } + name := v.Name() + if name == "" { + return nil, fmt.Errorf("invalid enum value %q", *v) + } + return json.Marshal(name) +} + +func (v *RegistryProtocol) UnmarshalJSON(dt []byte) error { + var s string + if err := json.Unmarshal(dt, &s); err != nil { + return err + } + switch s { + case "": + *v = "" + case "HTTP": + *v = RegistryProtocolHttp + case "HTTPS": + *v = RegistryProtocolHttps + default: + return fmt.Errorf("invalid enum value %q", s) + } + return nil +} + +const ( + RegistryProtocolHttps RegistryProtocol = "HTTPS" + + RegistryProtocolHttp RegistryProtocol = "HTTP" +) + +// Expected return type of an execution +type ReturnType string + +func (ReturnType) IsEnum() {} + +func (v ReturnType) Name() string { + switch v { + case ReturnTypeSuccess: + return "SUCCESS" + case ReturnTypeFailure: + return "FAILURE" + case ReturnTypeAny: + return "ANY" + default: + return "" + } +} + +func (v ReturnType) Value() string { + return string(v) +} + +func (v *ReturnType) MarshalJSON() ([]byte, error) { + if *v == "" { + return []byte(`""`), nil + } + name := v.Name() + if name == "" { + return nil, fmt.Errorf("invalid enum value %q", *v) + } + return json.Marshal(name) +} + +func (v *ReturnType) UnmarshalJSON(dt []byte) error { + var s string + if err := json.Unmarshal(dt, &s); err != nil { + return err + } + switch s { + case "": + *v = "" + case "ANY": + *v = ReturnTypeAny + case "FAILURE": + *v = ReturnTypeFailure + case "SUCCESS": + *v = ReturnTypeSuccess + default: + return fmt.Errorf("invalid enum value %q", s) + } + return nil +} + +const ( + // A successful execution (exit code 0) + ReturnTypeSuccess ReturnType = "SUCCESS" + + // A failed execution (exit codes 1-127 and 192-255) + ReturnTypeFailure ReturnType = "FAILURE" + + // Any execution (exit codes 0-127 and 192-255) + ReturnTypeAny ReturnType = "ANY" +) + +// Distinguishes the different kinds of TypeDefs. +type TypeDefKind string + +func (TypeDefKind) IsEnum() {} + +func (v TypeDefKind) Name() string { + switch v { + case TypeDefKindStringKind: + return "STRING_KIND" + case TypeDefKindIntegerKind: + return "INTEGER_KIND" + case TypeDefKindFloatKind: + return "FLOAT_KIND" + case TypeDefKindBooleanKind: + return "BOOLEAN_KIND" + case TypeDefKindScalarKind: + return "SCALAR_KIND" + case TypeDefKindListKind: + return "LIST_KIND" + case TypeDefKindObjectKind: + return "OBJECT_KIND" + case TypeDefKindInterfaceKind: + return "INTERFACE_KIND" + case TypeDefKindInputKind: + return "INPUT_KIND" + case TypeDefKindVoidKind: + return "VOID_KIND" + case TypeDefKindEnumKind: + return "ENUM_KIND" + default: + return "" + } +} + +func (v TypeDefKind) Value() string { + return string(v) +} + +func (v *TypeDefKind) MarshalJSON() ([]byte, error) { + if *v == "" { + return []byte(`""`), nil + } + name := v.Name() + if name == "" { + return nil, fmt.Errorf("invalid enum value %q", *v) + } + return json.Marshal(name) +} + +func (v *TypeDefKind) UnmarshalJSON(dt []byte) error { + var s string + if err := json.Unmarshal(dt, &s); err != nil { + return err + } + switch s { + case "": + *v = "" + case "BOOLEAN": + *v = TypeDefKindBoolean + case "BOOLEAN_KIND": + *v = TypeDefKindBooleanKind + case "ENUM": + *v = TypeDefKindEnum + case "ENUM_KIND": + *v = TypeDefKindEnumKind + case "FLOAT": + *v = TypeDefKindFloat + case "FLOAT_KIND": + *v = TypeDefKindFloatKind + case "INPUT": + *v = TypeDefKindInput + case "INPUT_KIND": + *v = TypeDefKindInputKind + case "INTEGER": + *v = TypeDefKindInteger + case "INTEGER_KIND": + *v = TypeDefKindIntegerKind + case "INTERFACE": + *v = TypeDefKindInterface + case "INTERFACE_KIND": + *v = TypeDefKindInterfaceKind + case "LIST": + *v = TypeDefKindList + case "LIST_KIND": + *v = TypeDefKindListKind + case "OBJECT": + *v = TypeDefKindObject + case "OBJECT_KIND": + *v = TypeDefKindObjectKind + case "SCALAR": + *v = TypeDefKindScalar + case "SCALAR_KIND": + *v = TypeDefKindScalarKind + case "STRING": + *v = TypeDefKindString + case "STRING_KIND": + *v = TypeDefKindStringKind + case "VOID": + *v = TypeDefKindVoid + case "VOID_KIND": + *v = TypeDefKindVoidKind + default: + return fmt.Errorf("invalid enum value %q", s) + } + return nil +} + +const ( + // A string value. + TypeDefKindStringKind TypeDefKind = "STRING_KIND" + // A string value. + TypeDefKindString TypeDefKind = TypeDefKindStringKind + + // An integer value. + TypeDefKindIntegerKind TypeDefKind = "INTEGER_KIND" + // An integer value. + TypeDefKindInteger TypeDefKind = TypeDefKindIntegerKind + + // A float value. + TypeDefKindFloatKind TypeDefKind = "FLOAT_KIND" + // A float value. + TypeDefKindFloat TypeDefKind = TypeDefKindFloatKind + + // A boolean value. + TypeDefKindBooleanKind TypeDefKind = "BOOLEAN_KIND" + // A boolean value. + TypeDefKindBoolean TypeDefKind = TypeDefKindBooleanKind + + // A scalar value of any basic kind. + TypeDefKindScalarKind TypeDefKind = "SCALAR_KIND" + // A scalar value of any basic kind. + TypeDefKindScalar TypeDefKind = TypeDefKindScalarKind + + // Always paired with a ListTypeDef. + // + // A list of values all having the same type. + TypeDefKindListKind TypeDefKind = "LIST_KIND" + // Always paired with a ListTypeDef. + // + // A list of values all having the same type. + TypeDefKindList TypeDefKind = TypeDefKindListKind + + // Always paired with an ObjectTypeDef. + // + // A named type defined in the GraphQL schema, with fields and functions. + TypeDefKindObjectKind TypeDefKind = "OBJECT_KIND" + // Always paired with an ObjectTypeDef. + // + // A named type defined in the GraphQL schema, with fields and functions. + TypeDefKindObject TypeDefKind = TypeDefKindObjectKind + + // Always paired with an InterfaceTypeDef. + // + // A named type of functions that can be matched+implemented by other objects+interfaces. + TypeDefKindInterfaceKind TypeDefKind = "INTERFACE_KIND" + // Always paired with an InterfaceTypeDef. + // + // A named type of functions that can be matched+implemented by other objects+interfaces. + TypeDefKindInterface TypeDefKind = TypeDefKindInterfaceKind + + // A graphql input type, used only when representing the core API via TypeDefs. + TypeDefKindInputKind TypeDefKind = "INPUT_KIND" + // A graphql input type, used only when representing the core API via TypeDefs. + TypeDefKindInput TypeDefKind = TypeDefKindInputKind + + // 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. + TypeDefKindVoidKind TypeDefKind = "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. + TypeDefKindVoid TypeDefKind = TypeDefKindVoidKind + + // A GraphQL enum type and its values + // + // Always paired with an EnumTypeDef. + TypeDefKindEnumKind TypeDefKind = "ENUM_KIND" + // A GraphQL enum type and its values + // + // Always paired with an EnumTypeDef. + TypeDefKindEnum TypeDefKind = TypeDefKindEnumKind +) + +type Client struct { + *Query + client graphql.Client +} + +var dag *Client + +func init() { + gqlClient, q := getClientParams() + dag = &Client{ + Query: &Query{ + query: q.Client(gqlClient), + }, + client: gqlClient, + } +} + +func Connect() *Client { + return dag +} + +// GraphQLClient returns the underlying graphql.Client +func (c *Client) GraphQLClient() graphql.Client { + return c.client +} + +// QueryBuilder returns the underlying query builder. +func (c *Client) QueryBuilder() *querybuilder.Selection { + return c.Query.query +} + +func getClientParams() (graphql.Client, *querybuilder.Selection) { + portStr, ok := os.LookupEnv("DAGGER_SESSION_PORT") + if !ok { + panic("DAGGER_SESSION_PORT is not set") + } + port, err := strconv.Atoi(portStr) + if err != nil { + panic(fmt.Errorf("DAGGER_SESSION_PORT %q is invalid: %w", portStr, err)) + } + + sessionToken := os.Getenv("DAGGER_SESSION_TOKEN") + if sessionToken == "" { + panic("DAGGER_SESSION_TOKEN is not set") + } + + host := fmt.Sprintf("127.0.0.1:%d", port) + + dialTransport := &http.Transport{ + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { + return net.Dial("tcp", host) + }, + } + httpClient := &http.Client{ + Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) { + r.SetBasicAuth(sessionToken, "") + + // detect $TRACEPARENT set by 'dagger run' + r = r.WithContext(fallbackSpanContext(r.Context())) + + // propagate span context via headers (i.e. for Dagger-in-Dagger) + telemetry.Propagator.Inject(r.Context(), propagation.HeaderCarrier(r.Header)) + + return dialTransport.RoundTrip(r) + }), + } + gqlClient := errorWrappedClient{graphql.NewClient(fmt.Sprintf("http://%s/query", host), httpClient)} + + return gqlClient, querybuilder.Query() +} + +func fallbackSpanContext(ctx context.Context) context.Context { + if trace.SpanContextFromContext(ctx).IsValid() { + return ctx + } + return telemetry.Propagator.Extract(ctx, telemetry.NewEnvCarrier(true)) +} + +// TODO: pollutes namespace, move to non internal package in dagger.io/dagger +type roundTripperFunc func(*http.Request) (*http.Response, error) + +func (fn roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return fn(req) +} + +type errorWrappedClient struct { + graphql.Client +} + +func (c errorWrappedClient) MakeRequest(ctx context.Context, req *graphql.Request, resp *graphql.Response) error { + err := c.Client.MakeRequest(ctx, req, resp) + if err != nil { + if e := getCustomError(err); e != nil { + return e + } + return err + } + return nil +} + +// selectNode returns a query selection for node(id:) scoped to the +// given type via an inline fragment. +func selectNode(q *querybuilder.Selection, id any, typeName string) *querybuilder.Selection { + return q.Select("node").Arg("id", id).InlineFragment(typeName) +} + +// Loadable is the constraint for types that can be loaded from an ID. +// Every generated object and interface client type satisfies this. +type Loadable[T any] interface { + Node + WithGraphQLQuery(*querybuilder.Selection) T +} + +// Ref returns a lazy reference to a node by its ID without making a +// network call. The returned value can be used to chain further queries. +func Ref[T Loadable[T]](c *Client, id ID) T { + var zero T + return zero.WithGraphQLQuery(selectNode(c.query, id, zero.XXX_GraphQLType())) +} + +// Load loads a node by its ID with type safety. It verifies that the +// node exists and matches the expected type before returning. +// +// The type parameter can be a concrete type or an interface. The +// __typename check is done through the inline fragment so that +// interface loads succeed when the concrete type (e.g. "Impl") +// implements the expected interface (e.g. "CustomIface"). +func Load[T Loadable[T]](ctx context.Context, c *Client, id ID) (T, error) { + var zero T + expectedType := zero.XXX_GraphQLType() + + // Query __typename through the inline fragment. For concrete types + // this is equivalent to a direct check. For interfaces, the fragment + // only matches if the concrete type implements the interface — an + // empty result means the ID doesn't satisfy the expected type. + q := selectNode(c.query, id, expectedType) + var typeName string + if err := q.Select("__typename").Bind(&typeName).Execute(ctx); err != nil { + return zero, fmt.Errorf("load %s: %w", expectedType, err) + } + if typeName == "" { + return zero, fmt.Errorf("load %s: node not found or does not implement %s", expectedType, expectedType) + } + + return zero.WithGraphQLQuery(selectNode(c.query, id, expectedType)), nil +} diff --git a/runtime/internal/dagger/python-sdk-runtime.gen.go b/runtime/internal/dagger/python-sdk-runtime.gen.go new file mode 100644 index 0000000..41f8b3c --- /dev/null +++ b/runtime/internal/dagger/python-sdk-runtime.gen.go @@ -0,0 +1,115 @@ +// Code generated by dagger. DO NOT EDIT. + +package dagger + +import ( + "context" + "encoding/json" + + "github.com/dagger/querybuilder" +) + +// State threaded through the steps that build a module's runtime container. +// +// ModuleRuntime is the only thing the engine calls; everything else here is +// internal to it. +type PythonSDKRuntime struct { // python-sdk-runtime (../../../:0:0) + query *querybuilder.Selection + + id *ID +} + +func (r *PythonSDKRuntime) WithGraphQLQuery(q *querybuilder.Selection) *PythonSDKRuntime { + return &PythonSDKRuntime{ + query: q, + } +} + +// A unique identifier for this PythonSdkRuntime. +func (r *PythonSDKRuntime) ID(ctx context.Context) (ID, error) { + if r.id != nil { + return *r.id, nil + } + q := r.query.Select("id") + + var response ID + + q = q.Bind(&response) + return response, q.Execute(ctx) +} + +// XXX_GraphQLType is an internal function. It returns the native GraphQL type name +func (r *PythonSDKRuntime) XXX_GraphQLType() string { + return "PythonSdkRuntime" +} + +// XXX_GraphQLIDType is an internal function. It returns the native GraphQL type name for the ID of this object +func (r *PythonSDKRuntime) XXX_GraphQLIDType() string { + return "ID" +} + +// XXX_GraphQLID is an internal function. It returns the underlying type ID +func (r *PythonSDKRuntime) XXX_GraphQLID(ctx context.Context) (string, error) { + id, err := r.ID(ctx) + if err != nil { + return "", err + } + return string(id), nil +} + +func (r *PythonSDKRuntime) MarshalJSON() ([]byte, error) { + id, err := r.ID(marshalCtx) + if err != nil { + return nil, err + } + return json.Marshal(id) +} +func (r *PythonSDKRuntime) UnmarshalJSON(bs []byte) error { + var id string + err := json.Unmarshal(bs, &id) + if err != nil { + return err + } + *r = PythonSDKRuntime{query: selectNode(dag.query, id, "PythonSdkRuntime")} + return nil +} + +// PythonSDKRuntimeModuleRuntimeOpts contains options for PythonSDKRuntime.ModuleRuntime +type PythonSDKRuntimeModuleRuntimeOpts struct { + IntrospectionJSON *File +} + +// Container for executing the Python module runtime +// +// The container is built from the module's committed generated files. This +// runtime generates nothing: code generation belongs to `dagger generate`, +// which the Python SDK module owns. Dependencies are still installed — the +// language-level assemble step, like the Go SDK still running go build. +// +// introspectionJSON is declared, and never read, on purpose: its optionality is +// the signal the engine reads (RuntimeTrustsCommittedFiles) to decide it may +// skip runtime codegen and omit the argument altogether. Dropping the argument +// would tell the engine the opposite. It is the opt-out, not a code path. +func (r *PythonSDKRuntime) ModuleRuntime(modSource *ModuleSource, opts ...PythonSDKRuntimeModuleRuntimeOpts) *Container { + assertNotNil("modSource", modSource) + q := r.query.Select("moduleRuntime") + for i := len(opts) - 1; i >= 0; i-- { + // `introspectionJson` optional argument + if !querybuilder.IsZeroValue(opts[i].IntrospectionJSON) { + q = q.Arg("introspectionJson", opts[i].IntrospectionJSON) + } + } + q = q.Arg("modSource", modSource) + + return &Container{ + query: q, + } +} + +func (r *Query) PythonSDKRuntime() *PythonSDKRuntime { // python-sdk-runtime (../../../:0:0) + q := r.query.Select("pythonSdkRuntime") + + return &PythonSDKRuntime{ + query: q, + } +} diff --git a/runtime/main.go b/runtime/main.go new file mode 100644 index 0000000..e2aa052 --- /dev/null +++ b/runtime/main.go @@ -0,0 +1,382 @@ +// Runtime module for the Python SDK + +package main + +import ( + "context" + "fmt" + "path" + + "python-sdk-runtime/internal/dagger" +) + +const ( + ModSourceDirPath = "/src" + RuntimeExecutablePath = "/runtime" + GenDir = "sdk" + SDKGenPath = "src/dagger/client/gen.py" + UserGenPath = "src/dagger_gen.py" + VenvPath = "/opt/venv" + ProjectCfg = "pyproject.toml" + PipCompileLock = "requirements.lock" + UvLock = "uv.lock" +) + +// 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() (*PythonSdkRuntime, error) { + d, err := NewDiscovery(UserConfig{ + UseUv: true, + }) + if err != nil { + return nil, err + } + return &PythonSdkRuntime{ + Discovery: d, + Container: dag.Container(), + // Where a module commits its vendored client library; a module that + // depends on a published dagger-io instead leaves this empty. + VendorPath: GenDir, + }, nil +} + +// State threaded through the steps that build a module's runtime container. +// +// ModuleRuntime is the only thing the engine calls; everything else here is +// internal to it. +type PythonSdkRuntime struct { + // Resulting container after each step + // +private + Container *dagger.Container + + // Whether the module runtime should run in debug mode. + // +private + Debug bool + + // The original module's name + // +private + ModName string + + // The normalized python distribution package name (in pyproject.toml) + // +private + ProjectName string + + // The normalized python import package name (in the filesystem) + // +private + PackageName string + + // The normalized main object name in Python + // +private + MainObjectName string + + // The source needed to load and run a module + // +private + 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. + // +private + 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. + // +private + ContextDirPath string + + // Relative path from the context directory to the source directory + // +private + SubPath string + + // Relative path the client library is vendored into, empty when the module + // depends on a published dagger-io instead + // +private + VendorPath string + + // True when the module has no pyproject.toml yet, so there is nothing to + // build from + // +private + IsInit bool + + // Discovery holds the logic for getting more information from the target module. + // +private + Discovery *Discovery +} + +// Container for executing the Python module runtime +// +// The container is built from the module's committed generated files. This +// runtime generates nothing: code generation belongs to `dagger generate`, +// which the Python SDK module owns. Dependencies are still installed — the +// language-level assemble step, like the Go SDK still running go build. +// +// introspectionJSON is declared, and never read, on purpose: its optionality is +// the signal the engine reads (RuntimeTrustsCommittedFiles) to decide it may +// skip runtime codegen and omit the argument altogether. Dropping the argument +// would tell the engine the opposite. It is the opt-out, not a code path. +func (m *PythonSdkRuntime) ModuleRuntime( + ctx context.Context, + modSource *dagger.ModuleSource, + // +optional + introspectionJSON *dagger.File, +) (*dagger.Container, error) { + if _, err := m.load(ctx, modSource); err != nil { + return nil, err + } + if m.IsInit { + return nil, fmt.Errorf("module %q has no source to trust; run `dagger generate` and commit the generated files", m.ModName) + } + if err := m.requireGeneratedFiles(ctx); err != nil { + return nil, err + } + if _, err := m.withBase(); err != nil { + return nil, err + } + runtime := m. + withRuntimeScript(). + withSource(). + withInstall() + ctr := runtime.Container + if runtime.Debug { + ctr = ctr.Terminal() + } + return ctr, nil +} + +// requireGeneratedFiles verifies the module's committed generated files are +// present, so a module that was never generated fails with an actionable error +// rather than an import error deep inside Python. +func (m *PythonSdkRuntime) requireGeneratedFiles(ctx context.Context) error { + // The generated bindings live inside the vendored library, or at + // UserGenPath for a module that depends on a published dagger-io. + required := []string{UserGenPath} + if m.VendorPath != "" { + required = []string{ + path.Join(m.VendorPath, ProjectCfg), + path.Join(m.VendorPath, SDKGenPath), + } + } + for _, rel := range required { + exists, err := m.source().Exists(ctx, rel) + if err != nil { + return fmt.Errorf("check generated file %q: %w", rel, err) + } + if exists { + continue + } + return fmt.Errorf( + "module %q: generated file %q is missing; run `dagger generate` and commit the generated files", + m.ModName, rel) + } + return nil +} + +// Get all the needed information from the module's metadata and source files +func (m *PythonSdkRuntime) load(ctx context.Context, modSource *dagger.ModuleSource) (*PythonSdkRuntime, error) { + m.ModSource = modSource + m.ContextDir = modSource.ContextDirectory() + sdkConfig, err := modSource.SDK(ctx) + if err != nil { + return nil, fmt.Errorf("runtime module load: %w", err) + } + debug, err := sdkConfig.Debug(ctx) + if err != nil { + return nil, fmt.Errorf("runtime module load: %w", err) + } + m.Debug = debug + + 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 *PythonSdkRuntime) withBase() (*PythonSdkRuntime, 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 *PythonSdkRuntime) uv() dagger.WithContainerFunc { + uvImage := m.getImage(UvImageName) + + return func(ctr *dagger.Container) *dagger.Container { + bins := dag.Container().From(uvImage.String()).Rootfs() + + return ctr. + WithMountedFile("/usr/local/bin/uv", bins.File("uv")). + WithMountedFile("/usr/local/bin/uvx", bins.File("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()) + } +} + +// withRuntimeScript mounts the runtime entrypoint script and sets it as the +// container entrypoint. +func (m *PythonSdkRuntime) withRuntimeScript() *PythonSdkRuntime { + m.Container = m.Container. + WithFile( + RuntimeExecutablePath, + dag.CurrentModule().Source().File("runtime.py"), + dagger.ContainerWithFileOpts{Permissions: 0o755}, + ). + WithEntrypoint([]string{RuntimeExecutablePath}) + return m +} + +// Add the module's source code +func (m *PythonSdkRuntime) withSource() *PythonSdkRuntime { + m.Container = m.Container. + WithWorkdir(path.Join(m.ContextDirPath, m.SubPath)). + WithMountedDirectory(m.ContextDirPath, m.ContextDir). + // Added as late as possible to avoid cache invalidation between + // different modules; the previous layer already depends on the target + // module's sources anyway. + WithEnvVariable("DAGGER_MODULE", m.ModName). + WithEnvVariable("DAGGER_DEFAULT_PYTHON_PACKAGE", m.PackageName). + WithEnvVariable("DAGGER_MAIN_OBJECT", m.MainObjectName) + return m +} + +// Install the module's package and dependencies +func (m *PythonSdkRuntime) withInstall() *PythonSdkRuntime { + // 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() { + // Trust the committed lockfile: fail loudly if it's stale instead of + // silently re-resolving. Nothing here ever rewrites it. + syncArgs := []string{"uv", "sync", "--no-dev", "--locked"} + // 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(syncArgs). + // 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 +} + +// Whether to install with uv rather than pip. +func (m *PythonSdkRuntime) useUv() bool { + return m.Discovery.UserConfig().UseUv +} + +// Uv's default index URL setting. +func (m *PythonSdkRuntime) 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 *PythonSdkRuntime) extraIndexURL() string { + for _, cfg := range m.Discovery.UvConfig().Index { + if cfg.Name != "" { + continue + } + if !cfg.Default { + return cfg.URL + } + } + return "" +} diff --git a/runtime/python.go b/runtime/python.go new file mode 100644 index 0000000..b711fd0 --- /dev/null +++ b/runtime/python.go @@ -0,0 +1,59 @@ +package main + +import ( + "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) +} diff --git a/runtime/python_test.go b/runtime/python_test.go new file mode 100644 index 0000000..4af05f0 --- /dev/null +++ b/runtime/python_test.go @@ -0,0 +1,57 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestProjectNameNormalization(t *testing.T) { + // Valid "dagger.json" names + inputs := []string{ + "friendly-bard", + "Friendly-Bard", + "FRIENDLY-BARD", + "friendly.bard", + "friendly_bard", + "friendly--bard", + "friendly-.bard", + "Friendly-..-bard", + "friendly--bard", + "_friendly . bard_", + "--friendly_bard--", + " friendly_bard ", + "friendly bard", + "Friendly Bard", + "friendlyBard", + } + for _, input := range inputs { + // require.Equal(t, "friendly-bard", NormalizeProjectName(input) + require.Equalf(t, "friendly-bard", NormalizeProjectNameFromModule(input), "input: %s", input) + } + require.Equal(t, "friendly-2", NormalizeProjectNameFromModule("friendly2")) +} + +func TestPackageNameNormalization(t *testing.T) { + // NormalizePackageName documents that it takes an already-normalized + // project name, so that is what it gets here; the messier module names it + // used to be handed directly are covered by TestProjectNameNormalization, + // which is the step that actually normalizes them. + require.Equal(t, "friendly_bard", NormalizePackageName("friendly-bard")) + require.Equal(t, "friendly_2", NormalizePackageName("friendly-2")) + + // The two steps compose: any module name reaches the same package name, + // which is the path discovery takes. + for _, input := range []string{ + "friendly-bard", + "Friendly-Bard", + "FRIENDLY-BARD", + "friendly.bard", + "friendly_bard", + "friendly--bard", + "friendlyBard", + } { + got := NormalizePackageName(NormalizeProjectNameFromModule(input)) + require.Equalf(t, "friendly_bard", got, "input: %s", input) + } +} diff --git a/runtime/runtime.py b/runtime/runtime.py new file mode 100755 index 0000000..ab9a302 --- /dev/null +++ b/runtime/runtime.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +import sys + +from dagger.mod.cli import app + +if __name__ == "__main__": + sys.exit(app(None, "--register" in sys.argv[1:])) diff --git a/sdk/.gitattributes b/sdk/.gitattributes new file mode 100644 index 0000000..88e577c --- /dev/null +++ b/sdk/.gitattributes @@ -0,0 +1,4 @@ +src/dagger/client/gen.py linguist-generated +src/dagger/_engine/_version.py linguist-generated +ruff_defaults.toml linguist-generated +/**/requirements*.lock linguist-generated diff --git a/sdk/.gitignore b/sdk/.gitignore new file mode 100644 index 0000000..5cd1f91 --- /dev/null +++ b/sdk/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.mypy_cache +.pytest_cache +.ruff_cache +dist +docs/_build +.venv diff --git a/sdk/.python-version b/sdk/.python-version new file mode 100644 index 0000000..6324d40 --- /dev/null +++ b/sdk/.python-version @@ -0,0 +1 @@ +3.14 diff --git a/sdk/LICENSE b/sdk/LICENSE new file mode 100644 index 0000000..17b2ba2 --- /dev/null +++ b/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/sdk/README.md b/sdk/README.md new file mode 100644 index 0000000..7b198bb --- /dev/null +++ b/sdk/README.md @@ -0,0 +1,158 @@ +# Dagger Python SDK + +> [!NOTE] +> This tree was taken from `dagger/dagger` at commit +> `501b57e0476dee5881b99a064c3c04173134ecc7`, path `sdk/python`. It is the +> client library and code generator that the runtime above it vendors into the +> modules it builds. `dagger/dagger` keeps its own copy to serve legacy +> `dagger.json` modules; the two are expected to diverge from here. +> Sphinx docs and changelog history were not carried over. Of the test suites, +> only `tests/codegen` and `tests/mod` came along; `tests/client` and +> `tests/provisioning` did not, so the vendored connection and provisioning +> code is carried without its tests. + +[![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 (run from this directory, `./sdk/python`, or adjust export path accordingly): + +```shell +dagger call -m ../../.dagger/modules/python-client-dev dev-container with-exec --args uv,lock,-U directory --path . export --path . +``` + +Update a single pinned development dependency (run from this directory, `./sdk/python`, or adjust export path accordingly): + +```shell +dagger call -m ../../.dagger/modules/python-client-dev dev-container with-exec --args uv,lock,-P, directory --path . export --path . +``` + +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/sdk/codegen/pyproject.toml b/sdk/codegen/pyproject.toml new file mode 100644 index 0000000..4227566 --- /dev/null +++ b/sdk/codegen/pyproject.toml @@ -0,0 +1,10 @@ +[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 = ["uv_build>=0.8.4,<0.9.0"] +build-backend = "uv_build" diff --git a/sdk/codegen/src/codegen/__init__.py b/sdk/codegen/src/codegen/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sdk/codegen/src/codegen/__main__.py b/sdk/codegen/src/codegen/__main__.py new file mode 100644 index 0000000..8221d0e --- /dev/null +++ b/sdk/codegen/src/codegen/__main__.py @@ -0,0 +1,5 @@ +import sys + +from codegen.cli import main + +sys.exit(main()) diff --git a/sdk/codegen/src/codegen/ast.py b/sdk/codegen/src/codegen/ast.py new file mode 100644 index 0000000..17016bb --- /dev/null +++ b/sdk/codegen/src/codegen/ast.py @@ -0,0 +1,110 @@ +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 = insert_field_stubs(tp, tp_schema) + + tp_schema.ast_node = graphql.ObjectTypeDefinitionNode( + fields=fields, + directives=parse_directives(tp["directives"]), + ) + + elif isinstance(tp_schema, graphql.GraphQLInterfaceType): + fields = insert_field_stubs(tp, tp_schema) + + tp_schema.ast_node = graphql.InterfaceTypeDefinitionNode( + fields=fields, + directives=parse_directives(tp["directives"]), + ) + + elif isinstance(tp_schema, graphql.GraphQLInputObjectType): + input_fields = [] + for field in tp["inputFields"]: + if field["name"] not in tp_schema.fields: + continue + field_schema = tp_schema.fields[field["name"]] + field_schema.ast_node = graphql.InputValueDefinitionNode( + name=graphql.NameNode(value=field["name"]), + description=field["description"], + directives=parse_directives(field["directives"]), + ) + input_fields.append(field_schema.ast_node) + + tp_schema.ast_node = graphql.InputObjectTypeDefinitionNode( + fields=input_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"]), + ) + + +def insert_field_stubs( + introspection_type: dict[str, Any], + schema_type: graphql.GraphQLObjectType | graphql.GraphQLInterfaceType, +) -> list[graphql.FieldDefinitionNode]: + fields = [] + for field in introspection_type["fields"]: + if field["name"] not in schema_type.fields: + continue + field_schema = schema_type.fields[field["name"]] + args = [] + for arg in field["args"]: + if arg["name"] not in field_schema.args: + continue + arg_schema = field_schema.args[arg["name"]] + arg_schema.ast_node = graphql.InputValueDefinitionNode( + name=graphql.NameNode(value=arg["name"]), + description=arg["description"], + directives=parse_directives(arg["directives"]), + ) + args.append(arg_schema.ast_node) + + field_schema.ast_node = graphql.FieldDefinitionNode( + name=graphql.NameNode(value=field["name"]), + description=field["description"], + arguments=args, + directives=parse_directives(field["directives"]), + ) + fields.append(field_schema.ast_node) + return fields + + +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/sdk/codegen/src/codegen/cli.py b/sdk/codegen/src/codegen/cli.py new file mode 100644 index 0000000..f61de09 --- /dev/null +++ b/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, schema_version=result.get("__schemaVersion", "")) + + 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/sdk/codegen/src/codegen/generator.py b/sdk/codegen/src/codegen/generator.py new file mode 100644 index 0000000..dc9f0f1 --- /dev/null +++ b/sdk/codegen/src/codegen/generator.py @@ -0,0 +1,1091 @@ +import enum +import functools +import itertools +import logging +import re +import textwrap +from abc import ABC, abstractmethod +from collections import defaultdict +from collections.abc import Callable, 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, + GraphQLInterfaceType, + 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.""" + + schema_version: str = "" + """Effective schema compatibility version.""" + + 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.""" + + @property + def legacy_sdk_compat(self) -> bool: + """Generate the pre-v0.21 ID/load helper source facade.""" + return legacy_sdk_compat(self.schema_version) + + 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, schema_version: str = "") -> 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 import Protocol, runtime_checkable + + 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, schema_version=schema_version) + + handlers: tuple[Handler, ...] = ( + Scalar(ctx), + Enum(ctx), + Input(ctx), + InterfaceProtocol(ctx), + Object(ctx), + ) + + if ctx.legacy_sdk_compat: + for type_name in legacy_id_names(schema): + yield legacy_id_class(type_name) + ctx.defined.add(type_name) + + # 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 "" + yield "class Client(Query):" + yield indent( + '"""The Dagger client.\n' + "\n" + "Inherits all Query API methods and adds connection management.\n" + '"""' + ) + ctx.defined.add("Client") + + 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]]: + named = get_named_type(t) + return is_list_type(t) and (is_object_type(named) or is_interface_type(named)) + + +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) and not isinstance(t, GraphQLInterfaceType) + + +def is_interface_type(t: GraphQLType) -> TypeGuard[GraphQLInterfaceType]: + return isinstance(t, GraphQLInterfaceType) + + +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, +) -> TypeGuard[GraphQLScalarType]: + t = get_named_type(t) + if not is_scalar_type(t): + return False + return t.name == "ID" + + +def id_from_type(t: GraphQLType) -> IDName | None: + """Return the id type name for the given type name.""" + return "ID" if is_id_type(t) else None + + +LEGACY_SDK_COMPAT_CUTOVER = (0, 21, 0) + + +def legacy_sdk_compat(schema_version: str) -> bool: + """Whether to render the pre-unified-ID Python SDK facade.""" + version = parse_version(schema_version) + return version is not None and version < LEGACY_SDK_COMPAT_CUTOVER + + +def parse_version(version: str) -> tuple[int, int, int] | None: + """Parse the numeric core of a Dagger semver string. + + We only need the major/minor/patch floor for the compatibility cutover: + v0.20.x is legacy, while every v0.21.0 prerelease/dev build is modern. + """ + match = re.match(r"^v?(\d+)\.(\d+)\.(\d+)", version) + if match is None: + return None + return tuple(int(part) for part in match.groups()) + + +def legacy_id_name(type_name: TypeName) -> IDName: + return f"{type_name}ID" + + +def legacy_idable_types( + schema: GraphQLSchema, +) -> list[GraphQLObjectType | GraphQLInterfaceType]: + types = [] + for t in schema.type_map.values(): + if not (is_object_type(t) or is_interface_type(t)): + continue + if t.name.startswith("_") or t.name == "Node": + continue + id_field = t.fields.get("id") + if id_field is None or not is_id_type(id_field.type): + continue + types.append(t) + return sorted(types, key=lambda t: t.name) + + +def legacy_id_names(schema: GraphQLSchema) -> Iterator[IDName]: + for t in legacy_idable_types(schema): + name = legacy_id_name(t.name) + if schema.get_type(name) is None: + yield name + + +def legacy_id_class(type_name: IDName) -> str: + return textwrap.dedent( + f'''\ + + class {type_name}(Scalar): + """Legacy typed ID alias for the unified ID scalar.""" + ''' + ) + + +def expected_type_name( + schema: GraphQLSchema, + node: graphql.language.ast.Node | None, +) -> TypeName | None: + """Extract the type name from an @expectedType directive on a field or argument.""" + if node is None: + return None + directive_def = schema.get_directive("expectedType") + if directive_def is None: + return None + args = graphql.get_directive_values(directive_def, node) + if args: + return args.get("name") + return 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 rewrite_notice(reason: str | None, prefix='"', suffix='"') -> str | None: + """Normalize deprecation/experimental messages and rewrite references.""" + if reason is None: + return None + + reason = reason.strip() + + def _format_name(match: re.Match[str]) -> str: + name = format_name(match.group(1)) + return f"{prefix}{name}{suffix}" + + return DEPRECATION_RE.sub(_format_name, reason) + + +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, + expected_type: TypeName | None = None, + legacy_ids: bool = False, +) -> 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): + inner = format_input_type(t.of_type, convert_id, expected_type, legacy_ids) + return fmt % f"list[{inner}]" + + if is_id_type(t): + if convert_id: + if expected_type is not None: + return fmt % expected_type + # Generic ID scalar — accept any Type (Dagger object) + return fmt % "Type" + if legacy_ids and expected_type is not None: + return fmt % legacy_id_name(expected_type) + + return fmt % (Scalars.from_type(t) if is_scalar_type(t) else get_named_type(t).name) + + +def format_output_type( + t: GraphQLOutputType, + expected_type: TypeName | None = None, + legacy_ids: bool = False, +) -> 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, expected_type, legacy_ids) + + +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 + ) + + # Read @expectedType directive from the argument's AST node. If an old + # schema view is being generated from the unified-ID schema and this is + # an `id` argument on a typed field, fall back to the field return type + # so the legacy signature can still use `FooID`. + self.expected_type = expected_type_name(ctx.schema, graphql.ast_node) + if ( + self.expected_type is None + and ctx.legacy_sdk_compat + and name == "id" + and self.parent_return_type not in (None, "Node") + ): + self.expected_type = self.parent_return_type + + # On object type fields, don't replace ID scalar with object + # only if field name is `id` and the expected type matches + # the output type (e.g., `file(id: ID! @expectedType(name: "File")) -> File`). + convert_id = not ( + name == "id" and self.expected_type == self.parent_return_type + ) + + self.type = format_input_type( + graphql.type, + convert_id, + self.expected_type, + ctx.legacy_sdk_compat, + ) + self.is_self = self.type == self.parent_object_name + self.description = graphql.description + self.has_default = graphql.default_value is not Undefined + reason = getattr(graphql, "deprecation_reason", None) + self.deprecated = rewrite_notice(reason, prefix="", suffix="") + + default_value = graphql.default_value + self.default_is_mutable = isinstance(default_value, list) + + 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: + self.default_value = repr(default_value) + + @joiner + def __str__(self) -> Iterator[str]: + """Output for an InputObject field.""" + yield "" + yield self.ctx.render_types(self.as_param()) + doc_parts: list[str] = [] + if self.description: + doc_parts.append(self.description) + if self.deprecated is not None: + directive = ".. deprecated::" + if self.deprecated: + directive = f"{directive} {self.deprecated}" + doc_parts.append(directive) + + if doc_parts: + yield doc("\n\n".join(doc_parts)) + + 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) + if self.deprecated is not None: + directive = ".. deprecated::" + if self.deprecated: + directive = f"{directive} {self.deprecated}" + yield from wrap_indent(directive) + + 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" + + # Read @expectedType directive from the field's AST node. + self.expected_type = expected_type_name(ctx.schema, field.ast_node) + legacy_output_id_type = self.expected_type + if ( + legacy_output_id_type is None + and ctx.legacy_sdk_compat + and name == "id" + and self.parent_name != "Node" + ): + legacy_output_id_type = self.parent_name + self.type = format_output_type( + field.type, + legacy_output_id_type, + ctx.legacy_sdk_compat, + ) + + # 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: ID! @expectedType(name: "Service") }` + # 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 + and self.expected_type + and self.parent_name == self.expected_type + ): + self.type = self.expected_type + 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: + # Use the concrete client class for interface types + t = self._iface_client_name(self.type) + yield f"return {t}(_ctx)" + elif self.is_list: + n = self.named_type.name + t = self._iface_client_name(n) + yield f"return await _ctx.execute_object_list({t})" + elif self.is_void: + yield "await _ctx.execute()" + else: + yield f"return await _ctx.execute({self.type})" + + def _iface_client_name(self, name: str) -> str: + """Return concrete client class name for interface types.""" + if is_interface_type(self.named_type): + return f"_{name}Client" + return name + + 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 or arg.deprecated is not None 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 | None: + return 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 rewrite_notice(reason, prefix, suffix) + + +@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 + + by_value = defaultdict(list) + for name, value in t.values.items(): + by_value[self._get_value(value)].append(name) + + for val, names in sorted(by_value.items()): + yield "" + + for name in names: + yield f"{name} = {val!r}" + + member = t.values[name] + desc = member.description + reason = rewrite_notice(member.deprecation_reason) + + doc_parts: list[str] = [] + if desc: + doc_parts.append(desc) + if reason is not None: + directive = ".. deprecated::" + if reason: + directive = f"{directive} {reason}" + doc_parts.append(directive) + + if doc_parts: + yield doc("\n\n".join(doc_parts)) + + def _get_value(self, value) -> str: + if value.ast_node and (directive := self.ctx.schema.get_directive("enumValue")): + args = graphql.get_directive_values(directive, value.ast_node) + if args: + return args["value"] + return value.value + + +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() + ) + + +@dataclass +class InterfaceProtocol(Handler[GraphQLInterfaceType]): + """Generate Protocol classes for GraphQL interfaces. + + Generates @runtime_checkable Protocol classes and a concrete + _FooClient(Type) class for query builder instantiation. + """ + + predicate: ClassVar[Predicate] = staticmethod(is_interface_type) + + def type_name(self, t: GraphQLInterfaceType) -> str: + return t.name + + def render_head(self, t: GraphQLInterfaceType) -> str: + return f"@runtime_checkable\nclass {t.name}(Protocol):" + + @joiner + def render(self, t: GraphQLInterfaceType) -> Iterator[str]: + # First: the Protocol class (for type annotations and isinstance checks) + yield "" + yield self.render_head(t) + yield indent(self.render_body(t)) + yield "" + + # Second: a concrete client class for query builder instantiation + client_name = f"_{t.name}Client" + yield "" + yield "@typecheck" + yield f"class {client_name}(Type):" + yield indent(f'"""Concrete client for {t.name} interface."""') + yield "" + # Override _graphql_name to return the interface name + yield indent("@classmethod") + yield indent("def _graphql_name(cls) -> str:") + yield indent(indent(f'return "{t.name}"')) + + # Generate method implementations using the Object handler's field rendering + for name, ifield in sorted(t.fields.items()): + obj_field = _ObjectField(self.ctx, name, ifield, t) + yield indent(str(obj_field)) + + yield "" + + @joiner + def render_body(self, t: GraphQLInterfaceType) -> Iterator[str]: + if t.description: + yield from wrap(doc(t.description)) + + for name, ifield in sorted(t.fields.items()): + if name == "id": + # id is available on all Type objects + continue + + obj_field = _ObjectField(self.ctx, name, ifield, t) + sig = obj_field.func_signature() + yield "" + yield f"{sig}" + if obj_field.description: + yield indent(doc(obj_field.description)) + else: + yield indent("...") + + +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) + + 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/sdk/pyproject.toml b/sdk/pyproject.toml new file mode 100644 index 0000000..9c811d3 --- /dev/null +++ b/sdk/pyproject.toml @@ -0,0 +1,100 @@ +[build-system] +requires = ["uv_build>=0.8.4,<0.12.0"] +build-backend = "uv_build" + +[project] +name = "dagger-io" +version = "0.0.0" +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", + "Programming Language :: Python :: 3.14", + "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]>=4.0", + # Avoid a broken release with only cp310 wheels. + "yarl!=1.24.1", + "httpcore>=1.0.8", + "beartype>=0.22.0", + "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", + "opentelemetry-instrumentation-logging>=0.54b1", + "exceptiongroup>=1.3.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" + +[dependency-groups] +dev = [ + "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.uv.build-backend] +module-name = "dagger" +source-include = ["tests/**", "LICENSE"] + +[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/sdk/ruff.toml b/sdk/ruff.toml new file mode 100644 index 0000000..4dd24d2 --- /dev/null +++ b/sdk/ruff.toml @@ -0,0 +1,122 @@ +src = ["src", "tests"] +target-version = "py310" + +[format] +docstring-code-format = true + +[lint] +select = ["ALL"] +ignore = [ + # Type inferrance is ok in a lot of places. + "ANN", + # This rule doesn't know to ignore a subclass override + # so we get false positives for unused arguments. + "ARG002", + # Can't control using a library with boolean arguments. + "FBT", + # TODO: Document public modules and packages. + "D100", + "D104", + "D105", + # Imperative mood only makes sense in functions, not classes. + "D401", + # Valid use in pytest and docs. + "INP001", + # We don't use asserts as runtime validation guarantees. + "S101", + # Don't guard types. Don't want to stringize them. + "TCH", + # Don't require author and link in TODO comments. + "TD002", + "TD003", + "FIX002", + # Pandas + "PD", + # Allow import inside functions to avoid circular imports issues. + "PLC0415", + # Avoid conflicts with the formatter + "W191", + "E111", + "E114", + "E117", + "D206", + "D300", + "Q000", + "Q002", + "Q003", + "COM812", + "COM819", + "ISC001", + "ISC002", +] + +[lint.per-file-ignores] +"!src/**.py" = [ + # No public interfaces. + "D1", + # First line should end with a period. + "D400", +] +"./codegen/*" = [ + # False positive on __str__ decorator. + "PLE0307", +] +"**/src/dagger/client/gen.py" = [ + # Not much control over field names and docs coming from the API. + # Note: We could detect built-in shadowing like the reserved + # keywords but these built-ins aren't being used in the generated + # code so no need to bother. + "A", + "D", + # Too hard to properly wrap long lines in codegen. + "E501", + # Allow access to private members as it's controlled by our own library. + "SLF001", + # Too many arguments to function call. + "PLR0913", +] +"codegen/gen.py" = [ + "A", + "D", + "E501", + "SLF001", + "PLR0913", +] +# Same as above, for dev module +"src/dagger_gen.py" = ["A", "D", "E501", "SLF001", "PLR0913"] +# Ignore built-in shadowing in test mocks. +"./tests/client/test_inputs.py" = ["A", "ERA001"] +"./tests/*.py" = [ + # Not using timezones in this project. + "DTZ", + # Magic value comparison doesn't apply to tests. + "PLR2004", + # Allow more than one statement in pytest.raises. + "PT012", + # Ignore security issues in tests. + "S", + # Allow accessing private fields in tests. + "SLF001", + # Allow covering `Optional` in tests. + "UP045", +] +# Allow alias imports in __init__. +"**/__init__.py" = ["PLC0414"] +# Allow some patterns to redefine imports in __init__. +"./src/**/__init__.py" = ["I001", "F403"] + +[lint.isort] +known-first-party = ["dagger", "codegen"] + +[lint.pycodestyle] +ignore-overlong-task-comments = true + +[lint.pydocstyle] +convention = "numpy" + +[lint.flake8-unused-arguments] +ignore-variadic-names = true + +[lint.flake8-pytest-style] +fixture-parentheses = false +mark-parentheses = false diff --git a/sdk/src/dagger/__init__.py b/sdk/src/dagger/__init__.py new file mode 100644 index 0000000..c82edfe --- /dev/null +++ b/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/sdk/src/dagger/_engine/_version.py b/sdk/src/dagger/_engine/_version.py new file mode 100644 index 0000000..9187ccd --- /dev/null +++ b/sdk/src/dagger/_engine/_version.py @@ -0,0 +1,3 @@ +# Code generated by dagger. DO NOT EDIT. + +CLI_VERSION = "1.0.0-beta.10" diff --git a/sdk/src/dagger/_exceptions.py b/sdk/src/dagger/_exceptions.py new file mode 100644 index 0000000..69849a1 --- /dev/null +++ b/sdk/src/dagger/_exceptions.py @@ -0,0 +1,180 @@ +import dataclasses +from typing import Any + +import cattrs +import gql +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: + msg = "Failed to establish client connection to the Dagger session" + if sup := super().__str__(): + return f"{msg}: {sup}" + return msg + + +class TransportError(ClientError): + """Error processing request/response during query execution.""" + + +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], request: gql.GraphQLRequest): + if not errors: + msg = "Errors list is empty" + raise ValueError(msg) + super().__init__(*errors) + self.errors: list[QueryErrorValue] = errors + self.request = request + self.query = request.document + + @property + def error(self) -> QueryErrorValue: + return self.errors[0] + + def __str__(self) -> str: + return str(self.error) + + 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, request: gql.GraphQLRequest): + """Create instance from a gql exception.""" + try: + errors = cattrs.structure(exc.errors, list[QueryErrorValue]) + except (TypeError, KeyError, ValueError): + return None + return QueryError(errors, request) 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) + + ext = self.error.extensions + self.command = ext["cmd"] + self.message = self.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", + "InvalidQueryError", + "QueryError", + "TransportError", + "VersionMismatch", +] diff --git a/sdk/src/dagger/_managers.py b/sdk/src/dagger/_managers.py new file mode 100644 index 0000000..3e3a9c6 --- /dev/null +++ b/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/sdk/src/dagger/client/__init__.py b/sdk/src/dagger/client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sdk/src/dagger/client/_config.py b/sdk/src/dagger/client/_config.py new file mode 100644 index 0000000..73dceb8 --- /dev/null +++ b/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/sdk/src/dagger/client/_connection.py b/sdk/src/dagger/client/_connection.py new file mode 100644 index 0000000..6640389 --- /dev/null +++ b/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/sdk/src/dagger/client/_core.py b/sdk/src/dagger/client/_core.py new file mode 100644 index 0000000..7e0fd38 --- /dev/null +++ b/sdk/src/dagger/client/_core.py @@ -0,0 +1,362 @@ +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 exceptiongroup +import gql +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, + DSLInlineFragment, + DSLQuery, + DSLSchema, + DSLSelectable, + DSLType, + dsl_gql, +) +from gql.transport.exceptions import ( + TransportClosed, + TransportConnectionFailed, + TransportProtocolError, + TransportQueryError, + TransportServerError, +) +from typing_extensions import TypeForm + +from dagger import ( + DaggerError, + 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) + # When set, children are wrapped in an inline fragment on this type: + # field(args) { ... on inline_type { children } } + inline_type: str | None = None + + def to_dsl(self, schema: DSLSchema) -> DSLField: + type_: DSLType = getattr(schema, self.type_name) + field_ = getattr(type_, self.name)(**self.args) + if self.children: + child_fields = { + name: child.to_dsl(schema) for name, child in self.children.items() + } + if self.inline_type is not None: + frag_type: DSLType = getattr(schema, self.inline_type) + inline = DSLInlineFragment().on(frag_type).select(**child_fields) + field_ = field_.select(inline) + else: + field_ = field_.select(**child_fields) + 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 != 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": + """Load an object by its ID via node(id:) with an inline fragment.""" + ctx = dataclasses.replace(self, selections=collections.deque()) + node_field = Field( + type_name="Query", + name="node", + args={"id": id_value}, + inline_type=type_name, + ) + selections = ctx.selections.copy() + selections.append(node_field) + return dataclasses.replace(ctx, selections=selections) + + 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. + try: + return root.to_dsl(DSLSchema(await self.conn.session.get_schema())) + except (graphql.GraphQLError, AttributeError, TypeError) as e: + logger.exception("GraphQL query builder failed to build query") + msg = ( + "Failed to build GraphQL query, probably due to a schema validation " + "issue. Please file a bug report because anything that could " + "fail to validate at this point should really happen sooner. " + "See Python logs for more details." + ) + raise InvalidQueryError(msg) from e + + async def request(self) -> gql.GraphQLRequest: + 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() + request = await self.request() + + try: + result = await self.conn.session.execute(request) + + 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 TransportConnectionFailed as e: + if not (msg := str(e)): + match e.__cause__: + case httpx.TimeoutException(): + msg = ( + "Request timed out. Try setting a higher timeout value " + "for this connection." + ) + case _: + msg = "Failed to execute request" + + raise TransportError(msg) from e + + except TransportQueryError as e: + if error := _query_error_from_transport(e, request): + 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]) + + gql_name = element_type._graphql_name() # noqa: SLF001 + return [element_type(ctx.select_id(gql_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._graphql_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) + + def handle_group_err(self, grp: exceptiongroup.BaseExceptionGroup): + """Handle exception group errors.""" + # just re-raise the first one + for exc in grp.exceptions: + raise exc from None + + 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 + with exceptiongroup.catch( + {(graphql.GraphQLError, DaggerError): self.handle_group_err} + ): + 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/sdk/src/dagger/client/_guards.py b/sdk/src/dagger/client/_guards.py new file mode 100644 index 0000000..e931117 --- /dev/null +++ b/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/sdk/src/dagger/client/_session.py b/sdk/src/dagger/client/_session.py new file mode 100644 index 0000000..b32b074 --- /dev/null +++ b/sdk/src/dagger/client/_session.py @@ -0,0 +1,259 @@ +import contextlib +import logging +import os +from dataclasses import dataclass, field +from typing import Any + +import gql +import graphql +import httpx +from gql.client import AsyncClientSession +from gql.client import Client as GraphQLClient +from gql.transport.exceptions import ( + TransportConnectionFailed, + 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 the schema for DSL query building, but don't + # validate queries client-side. The server validates with + # a corrected PossibleFragmentSpreads rule that handles + # interface-implements-interface; the graphql-core library + # used here does not, causing false rejections for + # `... on SomeIface` inside `node(id:)` when the interface + # has no concrete implementors in this schema view. + fetch_schema_from_transport=True, + # We're using the timeout from the httpx transport. + execute_timeout=None, + ) + # Disable client-side query validation. See comment above. + client.validate = lambda _request: None # type: ignore[method-assign] + + 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 TransportConnectionFailed as e: + raise ClientConnectionError(str(e)) 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: gql.GraphQLRequest) -> 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/sdk/src/dagger/client/base.py b/sdk/src/dagger/client/base.py new file mode 100644 index 0000000..2c6daaa --- /dev/null +++ b/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/sdk/src/dagger/client/gen.py b/sdk/src/dagger/client/gen.py new file mode 100644 index 0000000..ed8ad75 --- /dev/null +++ b/sdk/src/dagger/client/gen.py @@ -0,0 +1,16691 @@ +# Code generated by dagger. DO NOT EDIT. + +import warnings +from collections.abc import Callable +from dataclasses import dataclass +from typing import Protocol, runtime_checkable + +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 JSON(Scalar): + """An arbitrary JSON-encoded value.""" + + +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 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 ChangesetMergeConflict(Enum): + """Strategy to use when merging changesets with conflicting + changes.""" + + FAIL = "FAIL" + """Attempt the merge and fail if git merge fails due to conflicts""" + + FAIL_EARLY = "FAIL_EARLY" + """Fail before attempting merge if file-level conflicts are detected""" + + LEAVE_CONFLICT_MARKERS = "LEAVE_CONFLICT_MARKERS" + """Let git create conflict markers in files. For modify/delete conflicts, keeps the modified version. Fails on binary conflicts.""" + + PREFER_OURS = "PREFER_OURS" + """The conflict is resolved by applying the version of the calling changeset""" + + PREFER_THEIRS = "PREFER_THEIRS" + """The conflict is resolved by applying the version of the other changeset""" + + +class ChangesetsMergeConflict(Enum): + """Strategy to use when merging multiple changesets with git octopus + merge.""" + + FAIL = "FAIL" + """Attempt the octopus merge and fail if git merge fails due to conflicts""" + + FAIL_EARLY = "FAIL_EARLY" + """Fail before attempting merge if file-level conflicts are detected between any changesets""" + + +class DiffStatKind(Enum): + """The type of change for a diff stat entry.""" + + ADDED = "ADDED" + """A file or directory was added.""" + + MODIFIED = "MODIFIED" + """A file was modified.""" + + REMOVED = "REMOVED" + """A file or directory was removed.""" + + RENAMED = "RENAMED" + """A file was renamed.""" + + +class ExistsType(Enum): + """File type.""" + + DIRECTORY_TYPE = "DIRECTORY_TYPE" + """Tests path is a directory""" + + REGULAR_TYPE = "REGULAR_TYPE" + """Tests path is a regular file""" + + SYMLINK_TYPE = "SYMLINK_TYPE" + """Tests path is a symlink""" + + +class FileType(Enum): + """File type.""" + + DIRECTORY = "DIRECTORY" + """directory file type""" + DIRECTORY_TYPE = "DIRECTORY" + """directory file type""" + + REGULAR = "REGULAR" + """regular file type""" + REGULAR_TYPE = "REGULAR" + """regular file type""" + + SYMLINK = "SYMLINK" + """symlink file type""" + SYMLINK_TYPE = "SYMLINK" + """symlink file type""" + + UNKNOWN = "UNKNOWN" + """unknown file type""" + + +class FunctionCachePolicy(Enum): + """The behavior configured for function result caching.""" + + Default = "Default" + + Never = "Never" + + PerSession = "PerSession" + + +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.""" + + DockerMediaTypes = "DockerMediaTypes" + DOCKER = "DockerMediaTypes" + + OCIMediaTypes = "OCIMediaTypes" + OCI = "OCIMediaTypes" + + +class LLMContentBlockKind(Enum): + """The kind of content in a message block.""" + + TEXT = "TEXT" + """Plain text content.""" + + THINKING = "THINKING" + """Model thinking/reasoning content (e.g. Anthropic extended thinking).""" + + TOOL_CALL = "TOOL_CALL" + """A tool/function call from the model.""" + + TOOL_RESULT = "TOOL_RESULT" + """A tool/function result.""" + + +class LLMMessageRole(Enum): + """The role that generated a message.""" + + ASSISTANT = "ASSISTANT" + """A reply from the model.""" + + SYSTEM = "SYSTEM" + """A system prompt.""" + + USER = "USER" + """A user prompt or tool response.""" + + +class ModuleSourceExperimentalFeature(Enum): + """Experimental features of a module""" + + SELF_CALLS = "SELF_CALLS" + """Self calls""" + + +class ModuleSourceKind(Enum): + """The kind of module source.""" + + DIR_SOURCE = "DIR_SOURCE" + DIR = "DIR_SOURCE" + + GIT_SOURCE = "GIT_SOURCE" + GIT = "GIT_SOURCE" + + LOCAL_SOURCE = "LOCAL_SOURCE" + LOCAL = "LOCAL_SOURCE" + + +class NetworkProtocol(Enum): + """Transport layer network protocol associated to a port.""" + + TCP = "TCP" + + UDP = "UDP" + + +class PatchConflict(Enum): + """How to handle patch hunks that no longer apply to the target + content.""" + + FAIL = "FAIL" + """Fail the operation if any part of the patch does not apply.""" + + LEAVE_CONFLICT_MARKERS = "LEAVE_CONFLICT_MARKERS" + """Apply the hunks that fit and insert conflict markers where hunks no longer match, instead of failing.""" + + +class RegistryProtocol(Enum): + """Transport protocol to use for registry operations.""" + + HTTP = "HTTP" + + HTTPS = "HTTPS" + + +class ReturnType(Enum): + """Expected return type of an execution""" + + ANY = "ANY" + """Any execution (exit codes 0-127 and 192-255)""" + + FAILURE = "FAILURE" + """A failed execution (exit codes 1-127 and 192-255)""" + + SUCCESS = "SUCCESS" + """A successful execution (exit code 0)""" + + +class TypeDefKind(Enum): + """Distinguishes the different kinds of TypeDefs.""" + + BOOLEAN_KIND = "BOOLEAN_KIND" + """A boolean value.""" + BOOLEAN = "BOOLEAN_KIND" + """A boolean value.""" + + ENUM_KIND = "ENUM_KIND" + """A GraphQL enum type and its values + + Always paired with an EnumTypeDef. + """ + ENUM = "ENUM_KIND" + """A GraphQL enum type and its values + + Always paired with an EnumTypeDef. + """ + + FLOAT_KIND = "FLOAT_KIND" + """A float value.""" + FLOAT = "FLOAT_KIND" + """A float value.""" + + INPUT_KIND = "INPUT_KIND" + """A graphql input type, used only when representing the core API via TypeDefs.""" + INPUT = "INPUT_KIND" + """A graphql input type, used only when representing the core API via TypeDefs.""" + + INTEGER_KIND = "INTEGER_KIND" + """An integer value.""" + INTEGER = "INTEGER_KIND" + """An integer value.""" + + INTERFACE_KIND = "INTERFACE_KIND" + """Always paired with an InterfaceTypeDef. + + A named type of functions that can be matched+implemented by other objects+interfaces. + """ + INTERFACE = "INTERFACE_KIND" + """Always paired with an InterfaceTypeDef. + + A named type of functions that can be matched+implemented by other objects+interfaces. + """ + + LIST_KIND = "LIST_KIND" + """Always paired with a ListTypeDef. + + A list of values all having the same type. + """ + LIST = "LIST_KIND" + """Always paired with a ListTypeDef. + + A list of values all having the same type. + """ + + OBJECT_KIND = "OBJECT_KIND" + """Always paired with an ObjectTypeDef. + + A named type defined in the GraphQL schema, with fields and functions. + """ + OBJECT = "OBJECT_KIND" + """Always paired with an ObjectTypeDef. + + A named type defined in the GraphQL schema, with fields and functions. + """ + + SCALAR_KIND = "SCALAR_KIND" + """A scalar value of any basic kind.""" + SCALAR = "SCALAR_KIND" + """A scalar value of any basic kind.""" + + STRING_KIND = "STRING_KIND" + """A string value.""" + STRING = "STRING_KIND" + """A string value.""" + + 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. + """ + 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. + """ + + +@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 LLMContentBlockInput(Input): + """A content block within an LLM message.""" + + kind: LLMContentBlockKind + """The kind of content block.""" + + arguments: JSON | None = None + """The arguments to pass to the tool (for TOOL_CALL kind).""" + + call_id: str | None = "" + """The unique ID of a tool call (for TOOL_CALL or TOOL_RESULT kinds).""" + + errored: bool | None = False + """Whether the tool call resulted in an error (for TOOL_RESULT kind).""" + + signature: str | None = "" + """Provider-specific opaque data (e.g. Anthropic thinking signature).""" + + text: str | None = "" + """Text content (for TEXT, THINKING, or TOOL_RESULT kinds).""" + + tool_name: str | None = "" + """The name of the tool to call (for TOOL_CALL kind).""" + + +@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.""" + + +@runtime_checkable +class Exportable(Protocol): + """An object that can be exported to the host. Calling export writes + the object to a path on the host filesystem and returns the path that + was written.""" + + async def export(self, path: str) -> str: ... + + +@typecheck +class _ExportableClient(Type): + """Concrete client for Exportable interface.""" + + @classmethod + def _graphql_name(cls) -> str: + return "Exportable" + + async def export(self, path: str) -> str: + """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), + ] + _ctx = self._select("export", _args) + return await _ctx.execute(str) + + async def id(self) -> str: + """Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + +@runtime_checkable +class Node(Protocol): + """An object with a globally unique ID.""" + + +@typecheck +class _NodeClient(Type): + """Concrete client for Node interface.""" + + @classmethod + def _graphql_name(cls) -> str: + return "Node" + + async def id(self) -> str: + """Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + +@runtime_checkable +class Syncer(Protocol): + """An object that can be force-evaluated. Calling sync ensures that + the object's entire dependency DAG has been evaluated, returning the + object's ID once complete.""" + + async def sync(self) -> Self: ... + + +@typecheck +class _SyncerClient(Type): + """Concrete client for Syncer interface.""" + + @classmethod + def _graphql_name(cls) -> str: + return "Syncer" + + async def id(self) -> str: + """Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def sync(self) -> Self: + """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 Address(Type): + """A standardized address to load containers, directories, secrets, + and other object types. Address format depends on the type, and is + validated at type selection.""" + + def container(self) -> "Container": + """Load a container from the address.""" + _args: list[Arg] = [] + _ctx = self._select("container", _args) + return Container(_ctx) + + def directory( + self, + *, + exclude: list[str] | None = None, + include: list[str] | None = None, + gitignore: bool | None = False, + no_cache: bool | None = False, + ) -> "Directory": + """Load a directory from the address.""" + _args = [ + Arg("exclude", [] if exclude is None else exclude, []), + Arg("include", [] if include is None else include, []), + Arg("gitignore", gitignore, False), + Arg("noCache", no_cache, False), + ] + _ctx = self._select("directory", _args) + return Directory(_ctx) + + def file( + self, + *, + exclude: list[str] | None = None, + include: list[str] | None = None, + gitignore: bool | None = False, + no_cache: bool | None = False, + ) -> "File": + """Load a file from the address.""" + _args = [ + Arg("exclude", [] if exclude is None else exclude, []), + Arg("include", [] if include is None else include, []), + Arg("gitignore", gitignore, False), + Arg("noCache", no_cache, False), + ] + _ctx = self._select("file", _args) + return File(_ctx) + + def git_ref(self) -> "GitRef": + """Load a git ref (branch, tag or commit) from the address.""" + _args: list[Arg] = [] + _ctx = self._select("gitRef", _args) + return GitRef(_ctx) + + def git_repository(self) -> "GitRepository": + """Load a git repository from the address.""" + _args: list[Arg] = [] + _ctx = self._select("gitRepository", _args) + return GitRepository(_ctx) + + async def id(self) -> str: + """A unique identifier for this Address. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + def secret(self) -> "Secret": + """Load a secret from the address.""" + _args: list[Arg] = [] + _ctx = self._select("secret", _args) + return Secret(_ctx) + + def service(self) -> "Service": + """Load a service from the address.""" + _args: list[Arg] = [] + _ctx = self._select("service", _args) + return Service(_ctx) + + def socket(self) -> "Socket": + """Load a local socket from the address.""" + _args: list[Arg] = [] + _ctx = self._select("socket", _args) + return Socket(_ctx) + + async def value(self) -> str: + """The address 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) + + def volume(self) -> "Volume": + """Load a volume from the address.""" + _args: list[Arg] = [] + _ctx = self._select("volume", _args) + return Volume(_ctx) + + +@typecheck +class Agent(Type): + async def description(self) -> str: + """The description of the agent + + 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) -> str: + """A unique identifier for this Agent. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def name(self) -> str: + """Return the fully qualified name of the agent + + 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 original_module(self) -> "Module": + """The original module in which the agent has been defined""" + _args: list[Arg] = [] + _ctx = self._select("originalModule", _args) + return Module(_ctx) + + async def path(self) -> list[str]: + """The path of the agent within its module + + 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("path", _args) + return await _ctx.execute(list[str]) + + +@typecheck +class AgentGroup(Type): + def compose(self, *, base: "LLM | None" = None) -> "LLM": + """Compose all selected agent middlewares onto a base LLM, in + alphabetical module:fn order, and return the composed LLM. + + Parameters + ---------- + base: + The base LLM to compose onto. Defaults to a fresh workspace-bound + LLM. + """ + _args = [ + Arg("base", base, None), + ] + _ctx = self._select("compose", _args) + return LLM(_ctx) + + async def id(self) -> str: + """A unique identifier for this AgentGroup. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def list_(self) -> list[Agent]: + """Return a list of individual agents and their details""" + _args: list[Arg] = [] + _ctx = self._select("list", _args) + return await _ctx.execute_object_list(Agent) + + +@typecheck +class CacheVolume(Type): + """A directory whose contents persist across runs.""" + + async def id(self) -> str: + """A unique identifier for this CacheVolume. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + +@typecheck +class Changeset(Type): + """A comparison between two directories representing changes that can + be applied.""" + + async def added_paths(self) -> list[str]: + """Files and directories that were added in the newer directory. + + 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("addedPaths", _args) + return await _ctx.execute(list[str]) + + def after(self) -> "Directory": + """The newer/upper snapshot.""" + _args: list[Arg] = [] + _ctx = self._select("after", _args) + return Directory(_ctx) + + def as_patch(self) -> "File": + """Return a Git-compatible patch of the changes""" + _args: list[Arg] = [] + _ctx = self._select("asPatch", _args) + return File(_ctx) + + def before(self) -> "Directory": + """The older/lower snapshot to compare against.""" + _args: list[Arg] = [] + _ctx = self._select("before", _args) + return Directory(_ctx) + + async def diff_stats(self) -> list["DiffStat"]: + """Structured per-path diff statistics (kind and line counts) for this + changeset. + """ + _args: list[Arg] = [] + _ctx = self._select("diffStats", _args) + return await _ctx.execute_object_list(DiffStat) + + async def export(self, path: str) -> str: + """Applies the diff represented by this changeset to a path on the host. + + Parameters + ---------- + path: + Location of the copied directory (e.g., "logs/"). + + 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), + ] + _ctx = self._select("export", _args) + return await _ctx.execute(str) + + async def id(self) -> str: + """A unique identifier for this Changeset. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def is_empty(self) -> bool: + """Returns true if the changeset is empty (i.e. there are no changes). + + 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("isEmpty", _args) + return await _ctx.execute(bool) + + def layer(self) -> "Directory": + """Return a snapshot containing only the created and modified files""" + _args: list[Arg] = [] + _ctx = self._select("layer", _args) + return Directory(_ctx) + + async def modified_paths(self) -> list[str]: + """Files and directories that existed before and were updated in the + newer directory. + + 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("modifiedPaths", _args) + return await _ctx.execute(list[str]) + + async def removed_paths(self) -> list[str]: + """Files and directories that were removed. Directories are indicated by + a trailing slash, and their child paths are not included. + + 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("removedPaths", _args) + return await _ctx.execute(list[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 with_changeset( + self, + changes: Self, + *, + on_conflict: ChangesetMergeConflict | None = ChangesetMergeConflict.FAIL, + ) -> Self: + """Add changes to an existing changeset + + By default the operation will fail in case of conflicts, for instance + a file modified in both changesets. The behavior can be adjusted using + onConflict argument + + Parameters + ---------- + changes: + Changes to merge into the actual changeset + on_conflict: + What to do on a merge conflict + """ + _args = [ + Arg("changes", changes), + Arg("onConflict", on_conflict, ChangesetMergeConflict.FAIL), + ] + _ctx = self._select("withChangeset", _args) + return Changeset(_ctx) + + def with_changesets( + self, + changes: list["Changeset"], + *, + on_conflict: ChangesetsMergeConflict | None = ChangesetsMergeConflict.FAIL, + ) -> Self: + """Add changes from multiple changesets using git octopus merge strategy + + This is more efficient than chaining multiple withChangeset calls when + merging many changesets. + + Only FAIL and FAIL_EARLY conflict strategies are supported (octopus + merge cannot use -X ours/theirs). + + Parameters + ---------- + changes: + List of changesets to merge into the actual changeset + on_conflict: + What to do on a merge conflict + """ + _args = [ + Arg("changes", changes), + Arg("onConflict", on_conflict, ChangesetsMergeConflict.FAIL), + ] + _ctx = self._select("withChangesets", _args) + return Changeset(_ctx) + + def with_(self, cb: Callable[["Changeset"], "Changeset"]) -> "Changeset": + """Call the provided callable with current Changeset. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + +@typecheck +class Check(Type): + async def check_type(self) -> str: + """The type of check: 'check' for annotated checks, 'generate' for + generate-as-checks + + 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("checkType", _args) + return await _ctx.execute(str) + + async def completed(self) -> bool: + """Whether the check completed + + 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("completed", _args) + return await _ctx.execute(bool) + + async def description(self) -> str: + """The description of the check + + 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) + + def error(self) -> "Error": + """If the check failed, this is the error""" + _args: list[Arg] = [] + _ctx = self._select("error", _args) + return Error(_ctx) + + async def id(self) -> str: + """A unique identifier for this Check. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def name(self) -> str: + """Return the fully qualified name of the check + + 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 original_module(self) -> "Module": + """The original module in which the check has been defined""" + _args: list[Arg] = [] + _ctx = self._select("originalModule", _args) + return Module(_ctx) + + async def passed(self) -> bool: + """Whether the check passed + + 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("passed", _args) + return await _ctx.execute(bool) + + async def path(self) -> list[str]: + """The path of the check within its module + + 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("path", _args) + return await _ctx.execute(list[str]) + + async def result_emoji(self) -> str: + """An emoji representing the result of the check + + 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("resultEmoji", _args) + return await _ctx.execute(str) + + def run(self) -> Self: + """Execute the check""" + _args: list[Arg] = [] + _ctx = self._select("run", _args) + return Check(_ctx) + + def with_(self, cb: Callable[["Check"], "Check"]) -> "Check": + """Call the provided callable with current Check. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + +@typecheck +class CheckGroup(Type): + async def id(self) -> str: + """A unique identifier for this CheckGroup. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def list_(self) -> list[Check]: + """Return a list of individual checks and their details""" + _args: list[Arg] = [] + _ctx = self._select("list", _args) + return await _ctx.execute_object_list(Check) + + def report(self) -> "File": + """Generate a markdown report""" + _args: list[Arg] = [] + _ctx = self._select("report", _args) + return File(_ctx) + + def run(self, *, fail_fast: bool | None = None) -> Self: + """Execute all selected checks + + Parameters + ---------- + fail_fast: + If true, stop running checks as soon as any check fails. + """ + _args = [ + Arg("failFast", fail_fast, None), + ] + _ctx = self._select("run", _args) + return CheckGroup(_ctx) + + def with_(self, cb: Callable[["CheckGroup"], "CheckGroup"]) -> "CheckGroup": + """Call the provided callable with current CheckGroup. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + +@typecheck +class ClientFilesyncMirror(Type): + """An internal persistent filesync mirror.""" + + async def id(self) -> str: + """A unique identifier for this ClientFilesyncMirror. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + +@typecheck +class Cloud(Type): + """Dagger Cloud configuration and state""" + + async def id(self) -> str: + """A unique identifier for this Cloud. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) + + async def combined_output(self) -> str: + """The combined buffered standard output and 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("combinedOutput", _args) + return await _ctx.execute(str) + + 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) + + def docker_healthcheck(self) -> "HealthcheckConfig": + """Retrieves this container's configured docker healthcheck.""" + _args: list[Arg] = [] + _ctx = self._select("dockerHealthcheck", _args) + return HealthcheckConfig(_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 persistent 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 persistent environment variables configured on + the container. + """ + _args: list[Arg] = [] + _ctx = self._select("envVariables", _args) + return await _ctx.execute_object_list(EnvVariable) + + async def exists( + self, + path: str, + *, + expected_type: ExistsType | None = None, + do_not_follow_symlinks: bool | None = False, + expand: bool | None = False, + ) -> bool: + """check if a file or directory exists + + Parameters + ---------- + path: + Path to check (e.g., "/file.txt"). + expected_type: + If specified, also validate the type of file (e.g. "REGULAR_TYPE", + "DIRECTORY_TYPE", or "SYMLINK_TYPE"). + do_not_follow_symlinks: + If specified, do not follow symlinks. + 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 + ------- + 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 = [ + Arg("path", path), + Arg("expectedType", expected_type, None), + Arg("doNotFollowSymlinks", do_not_follow_symlinks, False), + Arg("expand", expand, False), + ] + _ctx = self._select("exists", _args) + return await _ctx.execute(bool) + + 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 export_image( + self, + name: str, + *, + platform_variants: "list[Container] | None" = None, + forced_compression: ImageLayerCompression | None = None, + media_types: ImageMediaTypes | None = ImageMediaTypes.OCIMediaTypes, + ) -> Void: + """Exports the container as an image to the host's container image store. + + Parameters + ---------- + name: + Name of image to export to in the host's store + 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. + + Returns + ------- + Void + 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("name", name), + Arg( + "platformVariants", + [] if platform_variants is None else platform_variants, + [], + ), + Arg("forcedCompression", forced_compression, None), + Arg("mediaTypes", media_types, ImageMediaTypes.OCIMediaTypes), + ] + _ctx = self._select("exportImage", _args) + await _ctx.execute() + + 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, + *, + registry_service: "Service | None" = None, + protocol: RegistryProtocol | None = None, + insecure_skip_tls_verify: bool | None = False, + ) -> 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" + registry_service: + Service to use as the registry endpoint for the image address. + The service will be started only for this pull. + protocol: + Protocol to use for registry communication. + Defaults to "HTTPS". Use "HTTP" only for plain HTTP registries. + insecure_skip_tls_verify: + Allow HTTPS registry communication without verifying the server + certificate. + """ + _args = [ + Arg("address", address), + Arg("registryService", registry_service, None), + Arg("protocol", protocol, None), + Arg("insecureSkipTLSVerify", insecure_skip_tls_verify, False), + ] + _ctx = self._select("from", _args) + return Container(_ctx) + + async def id(self) -> str: + """A unique identifier for this Container. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) + + def layer( + self, + id: str, + *, + forced_compression: ImageLayerCompression | None = None, + media_types: ImageMediaTypes | None = ImageMediaTypes.OCIMediaTypes, + ) -> "File": + """Returns the image layer or configuration blob with the given digest as + a File. + + Parameters + ---------- + id: + Digest of the layer or configuration blob (e.g. + "sha256:abc123..."). + 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: + Media types to use for image layers. Defaults to OCI. + """ + _args = [ + Arg("id", id), + Arg("forcedCompression", forced_compression, None), + Arg("mediaTypes", media_types, ImageMediaTypes.OCIMediaTypes), + ] + _ctx = self._select("layer", _args) + return File(_ctx) + + def manifest( + self, + *, + forced_compression: ImageLayerCompression | None = None, + media_types: ImageMediaTypes | None = ImageMediaTypes.OCIMediaTypes, + ) -> "File": + """Computes and returns the manifest for this container as a File. + + Parameters + ---------- + 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: + Media types to use for image layers. Defaults to OCI. + """ + _args = [ + Arg("forcedCompression", forced_compression, None), + Arg("mediaTypes", media_types, ImageMediaTypes.OCIMediaTypes), + ] + _ctx = self._select("manifest", _args) + return File(_ctx) + + 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, + registry_service: "Service | None" = None, + protocol: RegistryProtocol | None = None, + insecure_skip_tls_verify: bool | None = False, + ) -> 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. + registry_service: + Service to use as the registry endpoint for the image address. + The service will be started only for this push. + protocol: + Protocol to use for registry communication. + Defaults to "HTTPS". Use "HTTP" only for plain HTTP registries. + insecure_skip_tls_verify: + Allow HTTPS registry communication without verifying the server + certificate. + + 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), + Arg("registryService", registry_service, None), + Arg("protocol", protocol, None), + Arg("insecureSkipTLSVerify", insecure_skip_tls_verify, False), + ] + _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) + + def stat( + self, + path: str, + *, + do_not_follow_symlinks: bool | None = False, + ) -> "Stat": + """Return file status + + Parameters + ---------- + path: + Path to check (e.g., "/file.txt"). + do_not_follow_symlinks: + If specified, do not follow symlinks. + """ + _args = [ + Arg("path", path), + Arg("doNotFollowSymlinks", do_not_follow_symlinks, False), + ] + _ctx = self._select("stat", _args) + return Stat(_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 annotation. + + 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, + source: "Directory", + *, + exclude: list[str] | None = None, + include: list[str] | None = None, + gitignore: bool | None = False, + owner: str | None = "", + inherit_owner: bool | None = False, + expand: bool | None = False, + permissions: int | None = None, + ) -> Self: + """Return a new container snapshot, with a directory added to its + filesystem + + Parameters + ---------- + path: + Location of the written directory (e.g., "/tmp/directory"). + source: + 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"]). + gitignore: + Apply .gitignore rules when writing the directory. + 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. + inherit_owner: + Set the owner to the container's current 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"). + permissions: + """ + _args = [ + Arg("path", path), + Arg("source", source), + Arg("exclude", [] if exclude is None else exclude, []), + Arg("include", [] if include is None else include, []), + Arg("gitignore", gitignore, False), + Arg("owner", owner, ""), + Arg("inheritOwner", inherit_owner, False), + Arg("expand", expand, False), + Arg("permissions", permissions, None), + ] + _ctx = self._select("withDirectory", _args) + return Container(_ctx) + + def with_docker_healthcheck( + self, + args: list[str], + *, + shell: bool | None = None, + interval: str | None = None, + timeout: str | None = None, + start_period: str | None = None, + start_interval: str | None = None, + retries: int | None = None, + ) -> Self: + """Retrieves this container with the specificed docker healtcheck command + set. + + Parameters + ---------- + args: + Healthcheck command to execute. Example: ["go", "run", "main.go"]. + shell: + When true, command must be a single element, which is run using + the container's shell + interval: + Interval between running healthcheck. Example: "30s" + timeout: + Healthcheck timeout. Example: "3s" + start_period: + StartPeriod allows for failures during this initial startup period + which do not count towards maximum number of retries. Example: + "0s" + start_interval: + StartInterval configures the duration between checks during the + startup phase. Example: "5s" + retries: + The maximum number of consecutive failures before the container is + marked as unhealthy. Example: "3" + """ + _args = [ + Arg("args", args), + Arg("shell", shell, None), + Arg("interval", interval, None), + Arg("timeout", timeout, None), + Arg("startPeriod", start_period, None), + Arg("startInterval", start_interval, None), + Arg("retries", retries, None), + ] + _ctx = self._select("withDockerHealthcheck", _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_file_variables(self, source: "EnvFile") -> Self: + """Export environment variables from an env-file to the container. + + Parameters + ---------- + source: + Identifier of the envfile + """ + _args = [ + Arg("source", source), + ] + _ctx = self._select("withEnvFileVariables", _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_error(self, err: str) -> Self: + """Raise an error. + + Parameters + ---------- + err: + Message of the error to raise. If empty, the error will be + ignored. + """ + _args = [ + Arg("err", err), + ] + _ctx = self._select("withError", _args) + return Container(_ctx) + + def with_exec( + self, + args: list[str], + *, + use_entrypoint: bool | None = False, + stdin: str | None = "", + redirect_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_stdin: + Redirect the command's standard input from a file in the + container. Example: "./stdin.txt" + redirect_stdout: + Redirect the command's standard output to a file in the container. + Example: "./stdout.txt" + redirect_stderr: + Redirect the command's standard error to a file in the container. + Example: "./stderr.txt" + 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("redirectStdin", redirect_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 = "", + inherit_owner: bool | None = False, + 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. + inherit_owner: + Set the owner to the container's current 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("inheritOwner", inherit_owner, False), + 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 = "", + inherit_owner: bool | None = False, + 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. + inherit_owner: + Set the owner to the container's current 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("inheritOwner", inherit_owner, False), + 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 = "", + inherit_owner: bool | None = False, + 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. + inherit_owner: + Set the owner to the container's current 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("inheritOwner", inherit_owner, False), + Arg("expand", expand, False), + ] + _ctx = self._select("withMountedCache", _args) + return Container(_ctx) + + def with_mounted_directory( + self, + path: str, + source: "Directory", + *, + owner: str | None = "", + inherit_owner: bool | None = False, + read_only: bool | None = False, + 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. + inherit_owner: + Set the owner to the container's current user. + read_only: + Mount the directory read-only. + 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("inheritOwner", inherit_owner, False), + Arg("readOnly", read_only, False), + Arg("expand", expand, False), + ] + _ctx = self._select("withMountedDirectory", _args) + return Container(_ctx) + + def with_mounted_file( + self, + path: str, + source: "File", + *, + owner: str | None = "", + inherit_owner: bool | None = False, + 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. + inherit_owner: + Set the owner to the container's current 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("inheritOwner", inherit_owner, False), + Arg("expand", expand, False), + ] + _ctx = self._select("withMountedFile", _args) + return Container(_ctx) + + def with_mounted_secret( + self, + path: str, + source: "Secret", + *, + owner: str | None = "", + inherit_owner: bool | None = False, + 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. + inherit_owner: + Set the owner to the container's current 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("inheritOwner", inherit_owner, False), + 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_mounted_volume( + self, + path: str, + volume: "Volume", + *, + read_only: bool | None = False, + expand: bool | None = False, + ) -> Self: + """Retrieves this container plus a volume mounted at the given path. + + Parameters + ---------- + path: + Location of the volume mount (e.g., "/mnt/volume"). + volume: + Identifier of the volume to mount. + read_only: + Mount the volume read-only. + 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("volume", volume), + Arg("readOnly", read_only, False), + Arg("expand", expand, False), + ] + _ctx = self._select("withMountedVolume", _args) + return Container(_ctx) + + def with_new_file( + self, + path: str, + contents: str, + *, + permissions: int | None = 420, + owner: str | None = "", + inherit_owner: bool | None = False, + 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. + inherit_owner: + Set the owner to the container's current 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("inheritOwner", inherit_owner, False), + 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 = "", + inherit_owner: bool | None = False, + 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. + inherit_owner: + Set the owner to the container's current 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("inheritOwner", inherit_owner, False), + 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_volatile_variable(self, name: str, value: str) -> Self: + """Set a new non-secret environment variable for future execs without + invalidating exec cache when only its value changes. + + This is an expert-only escape hatch. If a volatile value affects + observable exec results, stale cached results may be reused. + + Parameters + ---------- + name: + Name of the volatile variable (e.g., "CI_RUN_ID"). + value: + Value of the volatile variable. + """ + _args = [ + Arg("name", name), + Arg("value", value), + ] + _ctx = self._select("withVolatileVariable", _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_docker_healthcheck(self) -> Self: + """Retrieves this container without a configured docker healtcheck + command. + """ + _args: list[Arg] = [] + _ctx = self._select("withoutDockerHealthcheck", _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_volatile_variable(self, name: str) -> Self: + """Retrieves this container minus the given volatile environment + variable. + + Parameters + ---------- + name: + The name of the volatile environment variable (e.g., "CI_RUN_ID"). + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("withoutVolatileVariable", _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.""" + + def as_sdk(self, workspace: "Workspace") -> "CurrentModuleAsSDK": + """Treat the currently executing module as an SDK installed in the given + workspace, exposing the modules and clients it manages. + + Errors if the current module is not installed as an SDK in this + workspace. + + Parameters + ---------- + workspace: + The workspace to resolve SDK-role data against. + """ + _args = [ + Arg("workspace", workspace), + ] + _ctx = self._select("asSDK", _args) + return CurrentModuleAsSDK(_ctx) + + 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) + + 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) + + def generators( + self, + *, + include: list[str] | None = None, + ) -> "GeneratorGroup": + """Return all generators defined by the module + + .. caution:: + Experimental: This API is highly experimental and may be removed + or replaced entirely. + + Parameters + ---------- + include: + Only include generators matching the specified patterns + """ + _args = [ + Arg("include", include, None), + ] + _ctx = self._select("generators", _args) + return GeneratorGroup(_ctx) + + async def id(self) -> str: + """A unique identifier for this CurrentModule. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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, + gitignore: bool | None = False, + ) -> "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.*"]). + gitignore: + Apply .gitignore filter rules inside the directory + """ + _args = [ + Arg("path", path), + Arg("exclude", [] if exclude is None else exclude, []), + Arg("include", [] if include is None else include, []), + Arg("gitignore", gitignore, False), + ] + _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 CurrentModuleAsSDK(Type): + """The SDK-role data for the currently executing module, as installed + in the supplied workspace.""" + + async def clients(self) -> list["CurrentModuleAsSDKClient"]: + """The generated clients this SDK produces in the workspace.""" + _args: list[Arg] = [] + _ctx = self._select("clients", _args) + return await _ctx.execute_object_list(CurrentModuleAsSDKClient) + + async def id(self) -> str: + """A unique identifier for this CurrentModuleAsSDK. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def modules(self) -> list["CurrentModuleAsSDKModule"]: + """The managed modules relevant to the bound workspace cwd: every module + at or below it, plus the nearest enclosing module when the cwd itself + is not managed. + """ + _args: list[Arg] = [] + _ctx = self._select("modules", _args) + return await _ctx.execute_object_list(CurrentModuleAsSDKModule) + + async def name(self) -> str: + """The user-facing name of this SDK in the workspace. + + 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 CurrentModuleAsSDKClient(Type): + """A generated client the current SDK produces in the workspace.""" + + async def id(self) -> str: + """A unique identifier for this CurrentModuleAsSDKClient. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def module(self) -> str: + """The module the client is bound to (workspace-relative path or + canonical 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("module", _args) + return await _ctx.execute(str) + + def module_source(self) -> "ModuleSource": + """The resolved module source this client is bound to, including its + dependency closure and pinned version. + """ + _args: list[Arg] = [] + _ctx = self._select("moduleSource", _args) + return ModuleSource(_ctx) + + async def path(self) -> str: + """Workspace-root-relative path of the generated client. + + 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("path", _args) + return await _ctx.execute(str) + + async def pin(self) -> str: + """The pinned version of the bound 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("pin", _args) + return await _ctx.execute(str) + + +@typecheck +class CurrentModuleAsSDKModule(Type): + """A workspace-local module managed by the current SDK.""" + + async def id(self) -> str: + """A unique identifier for this CurrentModuleAsSDKModule. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def path(self) -> str: + """Workspace-root-relative path to the managed 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("path", _args) + return await _ctx.execute(str) + + +@typecheck +class DiffStat(Type): + async def added_lines(self) -> int: + """Number of added lines for this path. + + 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("addedLines", _args) + return await _ctx.execute(int) + + async def id(self) -> str: + """A unique identifier for this DiffStat. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def kind(self) -> DiffStatKind: + """Type of change. + + Returns + ------- + DiffStatKind + The type of change for a diff stat entry. + + 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(DiffStatKind) + + async def old_path(self) -> str | None: + """Previous path of the file, set only for renames. + + 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("oldPath", _args) + return await _ctx.execute(str | None) + + async def path(self) -> str: + """Path of the changed file or 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("path", _args) + return await _ctx.execute(str) + + async def removed_lines(self) -> int: + """Number of removed lines for this path. + + 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("removedLines", _args) + return await _ctx.execute(int) + + +@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 as_workspace(self, *, cwd: str | None = "/") -> "Workspace": + """Creates a synthetic workspace from this directory. + + Parameters + ---------- + cwd: + Current working directory inside the workspace root. Defaults to + the workspace root. + """ + _args = [ + Arg("cwd", cwd, "/"), + ] + _ctx = self._select("asWorkspace", _args) + return Workspace(_ctx) + + def changes(self, from_: Self) -> Changeset: + """Return the difference between this directory and another directory, + typically an older snapshot. + + The difference is encoded as a changeset, which also tracks removed + files, and can be applied to other directories. + + Parameters + ---------- + from_: + The base directory snapshot to compare against + """ + _args = [ + Arg("from", from_), + ] + _ctx = self._select("changes", _args) + return Changeset(_ctx) + + def chown(self, path: str, owner: str) -> Self: + """Change the owner of the directory contents recursively. + + Parameters + ---------- + path: + Path of the directory to change ownership of (e.g., "/"). + 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. + """ + _args = [ + Arg("path", path), + Arg("owner", owner), + ] + _ctx = self._select("chown", _args) + return Directory(_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, + ssh: "Socket | None" = None, + ) -> 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. + ssh: + A socket to use for SSH authentication during the build + (e.g., for Dockerfile RUN --mount=type=ssh instructions). + Typically obtained via host.unixSocket() pointing to the + SSH_AUTH_SOCK. + """ + _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), + Arg("ssh", ssh, None), + ] + _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 exists( + self, + path: str, + *, + expected_type: ExistsType | None = None, + do_not_follow_symlinks: bool | None = False, + ) -> bool: + """check if a file or directory exists + + Parameters + ---------- + path: + Path to check (e.g., "/file.txt"). + expected_type: + If specified, also validate the type of file (e.g. "REGULAR_TYPE", + "DIRECTORY_TYPE", or "SYMLINK_TYPE"). + do_not_follow_symlinks: + If specified, do not follow symlinks. + + 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 = [ + Arg("path", path), + Arg("expectedType", expected_type, None), + Arg("doNotFollowSymlinks", do_not_follow_symlinks, False), + ] + _ctx = self._select("exists", _args) + return await _ctx.execute(bool) + + 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, + gitignore: bool | None = False, + ) -> 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.*"]). + gitignore: + If set, apply .gitignore rules when filtering the directory. + """ + _args = [ + Arg("exclude", [] if exclude is None else exclude, []), + Arg("include", [] if include is None else include, []), + Arg("gitignore", gitignore, False), + ] + _ctx = self._select("filter", _args) + return Directory(_ctx) + + async def find_up(self, name: str, start: str) -> str | None: + """Search up the directory tree for a file or directory, and return its + path. If no match, return null + + Parameters + ---------- + name: + The name of the file or directory to search for + start: + The path to start the search from + + 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), + Arg("start", start), + ] + _ctx = self._select("findUp", _args) + return await _ctx.execute(str | None) + + 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) -> str: + """A unique identifier for this Directory. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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 search( + self, + pattern: str, + *, + paths: list[str] | None = None, + globs: list[str] | None = None, + literal: bool | None = False, + multiline: bool | None = False, + dotall: bool | None = False, + insensitive: bool | None = False, + skip_ignored: bool | None = False, + skip_hidden: bool | None = False, + files_only: bool | None = False, + limit: int | None = None, + ) -> list["SearchResult"]: + """Searches for content matching the given regular expression or literal + string. + + Uses Rust regex syntax; escape literal ., [, ], {, }, | with + backslashes. + + Parameters + ---------- + pattern: + The text to match. + paths: + Directory or file paths to search + globs: + Glob patterns to match (e.g., "*.md") + literal: + Interpret the pattern as a literal string instead of a regular + expression. + multiline: + Enable searching across multiple lines. + dotall: + Allow the . pattern to match newlines in multiline mode. + insensitive: + Enable case-insensitive matching. + skip_ignored: + Honor .gitignore, .ignore, and .rgignore files. + skip_hidden: + Skip hidden files (files starting with .). + files_only: + Only return matching files, not lines and content + limit: + Limit the number of results to return + """ + _args = [ + Arg("pattern", pattern), + Arg("paths", [] if paths is None else paths, []), + Arg("globs", [] if globs is None else globs, []), + Arg("literal", literal, False), + Arg("multiline", multiline, False), + Arg("dotall", dotall, False), + Arg("insensitive", insensitive, False), + Arg("skipIgnored", skip_ignored, False), + Arg("skipHidden", skip_hidden, False), + Arg("filesOnly", files_only, False), + Arg("limit", limit, None), + ] + _ctx = self._select("search", _args) + return await _ctx.execute_object_list(SearchResult) + + def stat( + self, + path: str, + *, + do_not_follow_symlinks: bool | None = False, + ) -> "Stat": + """Return file status + + Parameters + ---------- + path: + Path to stat (e.g., "/file.txt"). + do_not_follow_symlinks: + If specified, do not follow symlinks. + """ + _args = [ + Arg("path", path), + Arg("doNotFollowSymlinks", do_not_follow_symlinks, False), + ] + _ctx = self._select("stat", _args) + return Stat(_ctx) + + 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_changes(self, changes: Changeset) -> Self: + """Return a directory with changes from another directory applied to it. + + Parameters + ---------- + changes: + Changes to apply to the directory + """ + _args = [ + Arg("changes", changes), + ] + _ctx = self._select("withChanges", _args) + return Directory(_ctx) + + def with_directory( + self, + path: str, + source: Self, + *, + exclude: list[str] | None = None, + include: list[str] | None = None, + gitignore: bool | None = False, + owner: str | None = "", + permissions: int | None = None, + ) -> Self: + """Return a snapshot with a directory added + + Parameters + ---------- + path: + Location of the written directory (e.g., "/src/"). + source: + 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.*"]). + gitignore: + Apply .gitignore filter rules inside the directory + owner: + A user:group to set for the copied 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. + permissions: + Permission given to the copied directory and contents (e.g., + 0755). + """ + _args = [ + Arg("path", path), + Arg("source", source), + Arg("exclude", [] if exclude is None else exclude, []), + Arg("include", [] if include is None else include, []), + Arg("gitignore", gitignore, False), + Arg("owner", owner, ""), + Arg("permissions", permissions, None), + ] + _ctx = self._select("withDirectory", _args) + return Directory(_ctx) + + def with_error(self, err: str) -> Self: + """Raise an error. + + Parameters + ---------- + err: + Message of the error to raise. If empty, the error will be + ignored. + """ + _args = [ + Arg("err", err), + ] + _ctx = self._select("withError", _args) + return Directory(_ctx) + + def with_file( + self, + path: str, + source: "File", + *, + permissions: int | None = None, + owner: str | 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). + owner: + A user:group to set for the copied 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. + """ + _args = [ + Arg("path", path), + Arg("source", source), + Arg("permissions", permissions, None), + Arg("owner", owner, ""), + ] + _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_patch( + self, + patch: str, + *, + on_conflict: PatchConflict | None = PatchConflict.FAIL, + ) -> Self: + """Retrieves this directory with the given Git-compatible patch applied. + + .. caution:: + Experimental: This API is highly experimental and may be removed + or replaced entirely. + + Parameters + ---------- + patch: + Patch to apply (e.g., "diff --git a/file.txt b/file.txt\nindex + 1234567..abcdef8 100644\n--- a/file.txt\n+++ b/file.txt\n@@ -1,1 + +1,1 @@\n-Hello\n+World\n"). + on_conflict: + How to handle hunks that no longer apply to the target content: + fail (default), or apply what fits and leave git-style conflict + markers where it doesn't. + """ + _args = [ + Arg("patch", patch), + Arg("onConflict", on_conflict, PatchConflict.FAIL), + ] + _ctx = self._select("withPatch", _args) + return Directory(_ctx) + + def with_patch_file( + self, + patch: "File", + *, + on_conflict: PatchConflict | None = PatchConflict.FAIL, + ) -> Self: + """Retrieves this directory with the given Git-compatible patch file + applied. + + .. caution:: + Experimental: This API is highly experimental and may be removed + or replaced entirely. + + Parameters + ---------- + patch: + File containing the patch to apply + on_conflict: + How to handle hunks that no longer apply to the target content: + fail (default), or apply what fits and leave git-style conflict + markers where it doesn't. + """ + _args = [ + Arg("patch", patch), + Arg("onConflict", on_conflict, PatchConflict.FAIL), + ] + _ctx = self._select("withPatchFile", _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 Engine(Type): + """The Dagger engine configuration and state""" + + async def clients(self) -> list[str]: + """The list of connected client IDs + + 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("clients", _args) + return await _ctx.execute(list[str]) + + async def id(self) -> str: + """A unique identifier for this Engine. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + def local_cache(self) -> "EngineCache": + """The local engine cache state tracked by dagql""" + _args: list[Arg] = [] + _ctx = self._select("localCache", _args) + return EngineCache(_ctx) + + async def name(self) -> str: + """The name of the engine 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("name", _args) + return await _ctx.execute(str) + + +@typecheck +class EngineCache(Type): + """A cache storage for the Dagger engine""" + + def entry_set(self, *, key: str | None = "") -> "EngineCacheEntrySet": + """The current set of entries in the cache""" + _args = [ + Arg("key", key, ""), + ] + _ctx = self._select("entrySet", _args) + return EngineCacheEntrySet(_ctx) + + async def id(self) -> str: + """A unique identifier for this EngineCache. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def max_used_space(self) -> int: + """The maximum bytes to keep in the cache without pruning. + + 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("maxUsedSpace", _args) + return await _ctx.execute(int) + + async def min_free_space(self) -> int: + """The target amount of free disk space the garbage collector will + attempt to leave. + + 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("minFreeSpace", _args) + return await _ctx.execute(int) + + async def prune( + self, + *, + use_default_policy: bool | None = False, + max_used_space: str | None = "", + reserved_space: str | None = "", + min_free_space: str | None = "", + target_space: str | None = "", + max_estimated_bytes: int | None = None, + target_estimated_bytes: int | None = None, + ) -> Void | None: + """Prune the cache of releaseable entries + + Parameters + ---------- + use_default_policy: + Use enabled engine-wide default disk and structural policies. If + no default disk policy is enabled, the disk stage falls back to + pruning all releasable disk-cache entries. If false, explicit + options select stages; with no options, all releasable disk-cache + entries are pruned. + max_used_space: + Override the maximum disk space to keep before pruning (e.g. + "200GB" or "80%"). + reserved_space: + Override the minimum disk space to retain during pruning (e.g. + "500GB" or "10%"). + min_free_space: + Override the minimum free disk space target during pruning (e.g. + "20GB" or "20%"). + target_space: + Override the target disk space to keep after pruning (e.g. "200GB" + or "50%"). + max_estimated_bytes: + Override the maximum structural metadata estimate in absolute + bytes. Explicit values must be positive; the configured/default + value is used when omitted. + target_estimated_bytes: + Override the structural metadata estimate to target in absolute + bytes. Explicit values must be positive and lower than the + resolved maximum; the configured/default value is used when + omitted. + + 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("useDefaultPolicy", use_default_policy, False), + Arg("maxUsedSpace", max_used_space, ""), + Arg("reservedSpace", reserved_space, ""), + Arg("minFreeSpace", min_free_space, ""), + Arg("targetSpace", target_space, ""), + Arg("maxEstimatedBytes", max_estimated_bytes, None), + Arg("targetEstimatedBytes", target_estimated_bytes, None), + ] + _ctx = self._select("prune", _args) + await _ctx.execute() + + async def reserved_space(self) -> int: + """The minimum amount of disk space this policy is guaranteed to retain. + + 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("reservedSpace", _args) + return await _ctx.execute(int) + + async def target_space(self) -> int: + """The target number of bytes to keep when pruning. + + 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("targetSpace", _args) + return await _ctx.execute(int) + + +@typecheck +class EngineCacheEntry(Type): + """An individual cache entry in a cache entry set""" + + async def actively_used(self) -> bool: + """Whether the cache entry is actively being used. + + 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("activelyUsed", _args) + return await _ctx.execute(bool) + + async def created_time_unix_nano(self) -> int: + """The time the cache entry was created, in Unix nanoseconds. + + 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("createdTimeUnixNano", _args) + return await _ctx.execute(int) + + async def dagql_call(self) -> str: + """The DagQL call that produced this cache entry. + + 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("dagqlCall", _args) + return await _ctx.execute(str) + + async def description(self) -> str: + """The description of the cache entry. + + 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 disk_space_bytes(self) -> int: + """The disk space used by the cache entry. + + 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("diskSpaceBytes", _args) + return await _ctx.execute(int) + + async def id(self) -> str: + """A unique identifier for this EngineCacheEntry. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def most_recent_use_time_unix_nano(self) -> int: + """The most recent time the cache entry was used, in Unix nanoseconds. + + 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("mostRecentUseTimeUnixNano", _args) + return await _ctx.execute(int) + + async def record_type(self) -> str: + """The type of the cache record (e.g. regular, internal, frontend, + source.local, source.git.checkout, exec.cachemount). + + 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("recordType", _args) + return await _ctx.execute(str) + + async def record_types(self) -> list[str]: + """The storage record types represented by this cache entry. + + 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("recordTypes", _args) + return await _ctx.execute(list[str]) + + +@typecheck +class EngineCacheEntrySet(Type): + """A set of cache entries returned by a query to a cache""" + + async def disk_space_bytes(self) -> int: + """The total disk space used by the cache entries in this set. + + 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("diskSpaceBytes", _args) + return await _ctx.execute(int) + + async def entries(self) -> list[EngineCacheEntry]: + """The list of individual cache entries in the set""" + _args: list[Arg] = [] + _ctx = self._select("entries", _args) + return await _ctx.execute_object_list(EngineCacheEntry) + + async def entry_count(self) -> int: + """The number of cache entries in this set. + + 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("entryCount", _args) + return await _ctx.execute(int) + + async def id(self) -> str: + """A unique identifier for this EngineCacheEntrySet. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + +@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) -> str: + """A unique identifier for this EnumTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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"]: + """The members of the enum. + + .. 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 deprecated(self) -> str | None: + """The reason this enum member is deprecated, if any. + + 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("deprecated", _args) + return await _ctx.execute(str | None) + + 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) -> str: + """A unique identifier for this EnumValueTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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 EnvFile(Type): + """A collection of environment variables.""" + + def as_file(self) -> "File": + """Return as a file""" + _args: list[Arg] = [] + _ctx = self._select("asFile", _args) + return File(_ctx) + + async def exists(self, name: str) -> bool: + """Check if a variable exists + + Parameters + ---------- + name: + Variable name + + 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 = [ + Arg("name", name), + ] + _ctx = self._select("exists", _args) + return await _ctx.execute(bool) + + async def get( + self, + name: str, + *, + raw: bool | None = None, + ) -> str: + """Lookup a variable (last occurrence wins) and return its value, or an + empty string + + Parameters + ---------- + name: + Variable name + raw: + Return the value exactly as written to the file. No quote removal + or variable expansion + + 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("name", name), + Arg("raw", raw, None), + ] + _ctx = self._select("get", _args) + return await _ctx.execute(str) + + async def id(self) -> str: + """A unique identifier for this EnvFile. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + def namespace(self, prefix: str) -> Self: + """Filters variables by prefix and removes the pref from keys. Variables + without the prefix are excluded. For example, with the prefix + "MY_APP_" and variables: MY_APP_TOKEN=topsecret MY_APP_NAME=hello + FOO=bar the resulting environment will contain: TOKEN=topsecret + NAME=hello + + Parameters + ---------- + prefix: + The prefix to filter by + """ + _args = [ + Arg("prefix", prefix), + ] + _ctx = self._select("namespace", _args) + return EnvFile(_ctx) + + async def variables(self, *, raw: bool | None = None) -> list["EnvVariable"]: + """Return all variables + + Parameters + ---------- + raw: + Return values exactly as written to the file. No quote removal or + variable expansion + """ + _args = [ + Arg("raw", raw, None), + ] + _ctx = self._select("variables", _args) + return await _ctx.execute_object_list(EnvVariable) + + def with_variable(self, name: str, value: str) -> Self: + """Add a variable + + Parameters + ---------- + name: + Variable name + value: + Variable value + """ + _args = [ + Arg("name", name), + Arg("value", value), + ] + _ctx = self._select("withVariable", _args) + return EnvFile(_ctx) + + def without_variable(self, name: str) -> Self: + """Remove all occurrences of the named variable + + Parameters + ---------- + name: + Variable name + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("withoutVariable", _args) + return EnvFile(_ctx) + + def with_(self, cb: Callable[["EnvFile"], "EnvFile"]) -> "EnvFile": + """Call the provided callable with current EnvFile. + + 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) -> str: + """A unique identifier for this EnvVariable. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) -> str: + """A unique identifier for this Error. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) -> str: + """A unique identifier for this ErrorValue. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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 deprecated(self) -> str | None: + """The reason this enum member is deprecated, if any. + + 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("deprecated", _args) + return await _ctx.execute(str | None) + + 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) -> str: + """A unique identifier for this FieldTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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.""" + + def as_env_file(self, *, expand: bool | None = None) -> EnvFile: + """Parse as an env file + + Parameters + ---------- + expand: + Replace "${VAR}" or "$VAR" with the value of other vars + .. deprecated:: Variable expansion is now enabled by default + """ + _args = [ + Arg("expand", expand, None), + ] + _ctx = self._select("asEnvFile", _args) + return EnvFile(_ctx) + + def as_json(self) -> "JSONValue": + """Parse the file contents as JSON.""" + _args: list[Arg] = [] + _ctx = self._select("asJSON", _args) + return JSONValue(_ctx) + + def chown(self, owner: str) -> Self: + """Change the owner of the file recursively. + + Parameters + ---------- + 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. + """ + _args = [ + Arg("owner", owner), + ] + _ctx = self._select("chown", _args) + return File(_ctx) + + async def contents( + self, + *, + offset_lines: int | None = None, + limit_lines: int | None = None, + ) -> str: + """Retrieves the contents of the file. + + Parameters + ---------- + offset_lines: + Start reading after this line + limit_lines: + Maximum number of lines to read + + 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("offsetLines", offset_lines, None), + Arg("limitLines", limit_lines, None), + ] + _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) -> str: + """A unique identifier for this File. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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 search( + self, + pattern: str, + *, + literal: bool | None = False, + multiline: bool | None = False, + dotall: bool | None = False, + insensitive: bool | None = False, + skip_ignored: bool | None = False, + skip_hidden: bool | None = False, + files_only: bool | None = False, + limit: int | None = None, + paths: list[str] | None = None, + globs: list[str] | None = None, + ) -> list["SearchResult"]: + """Searches for content matching the given regular expression or literal + string. + + Uses Rust regex syntax; escape literal ., [, ], {, }, | with + backslashes. + + Parameters + ---------- + pattern: + The text to match. + literal: + Interpret the pattern as a literal string instead of a regular + expression. + multiline: + Enable searching across multiple lines. + dotall: + Allow the . pattern to match newlines in multiline mode. + insensitive: + Enable case-insensitive matching. + skip_ignored: + Honor .gitignore, .ignore, and .rgignore files. + skip_hidden: + Skip hidden files (files starting with .). + files_only: + Only return matching files, not lines and content + limit: + Limit the number of results to return + paths: + globs: + """ + _args = [ + Arg("pattern", pattern), + Arg("literal", literal, False), + Arg("multiline", multiline, False), + Arg("dotall", dotall, False), + Arg("insensitive", insensitive, False), + Arg("skipIgnored", skip_ignored, False), + Arg("skipHidden", skip_hidden, False), + Arg("filesOnly", files_only, False), + Arg("limit", limit, None), + Arg("paths", [] if paths is None else paths, []), + Arg("globs", [] if globs is None else globs, []), + ] + _ctx = self._select("search", _args) + return await _ctx.execute_object_list(SearchResult) + + 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) + + def stat(self) -> "Stat": + """Return file status""" + _args: list[Arg] = [] + _ctx = self._select("stat", _args) + return Stat(_ctx) + + 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_replaced( + self, + search: str, + replacement: str, + *, + all: bool | None = False, + first_from: int | None = None, + ) -> Self: + """Retrieves the file with content replaced with the given text. + + If 'all' is true, all occurrences of the pattern will be replaced. + + If 'firstAfter' is specified, only the first match starting at the + specified line will be replaced. + + If neither are specified, and there are multiple matches for the + pattern, this will error. + + If there are no matches for the pattern, this will error. + + Parameters + ---------- + search: + The text to match. + replacement: + The text to match. + all: + Replace all occurrences of the pattern. + first_from: + Replace the first match starting from the specified line. + """ + _args = [ + Arg("search", search), + Arg("replacement", replacement), + Arg("all", all, False), + Arg("firstFrom", first_from, None), + ] + _ctx = self._select("withReplaced", _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 deprecated(self) -> str | None: + """The reason this function is deprecated, if any. + + 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("deprecated", _args) + return await _ctx.execute(str | None) + + 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) -> str: + """A unique identifier for this Function. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) + + async def source_module_name(self) -> str: + """If this function is provided by 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) + + def with_agent(self) -> Self: + """Returns the function with a flag indicating it is an agent middleware.""" + _args: list[Arg] = [] + _ctx = self._select("withAgent", _args) + return Function(_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, + deprecated: str | None = None, + default_address: str | 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. + deprecated: + If deprecated, the reason or migration path. + default_address: + """ + _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), + Arg("deprecated", deprecated, None), + Arg("defaultAddress", default_address, ""), + ] + _ctx = self._select("withArg", _args) + return Function(_ctx) + + def with_cache_policy( + self, + policy: FunctionCachePolicy, + *, + time_to_live: str | None = None, + ) -> Self: + """Returns the function updated to use the provided cache policy. + + Parameters + ---------- + policy: + The cache policy to use. + time_to_live: + The TTL for the cache policy, if applicable. Provided as a + duration string, e.g. "5m", "1h30s". + """ + _args = [ + Arg("policy", policy), + Arg("timeToLive", time_to_live, None), + ] + _ctx = self._select("withCachePolicy", _args) + return Function(_ctx) + + def with_check(self) -> Self: + """Returns the function with a flag indicating it's a check.""" + _args: list[Arg] = [] + _ctx = self._select("withCheck", _args) + return Function(_ctx) + + def with_deprecated(self, *, reason: str | None = None) -> Self: + """Returns the function with the provided deprecation reason. + + Parameters + ---------- + reason: + Reason or migration path describing the deprecation. + """ + _args = [ + Arg("reason", reason, None), + ] + _ctx = self._select("withDeprecated", _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_generator(self) -> Self: + """Returns the function with a flag indicating it's a generator.""" + _args: list[Arg] = [] + _ctx = self._select("withGenerator", _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_up(self) -> Self: + """Returns the function with a flag indicating it returns a service for + dagger up. + """ + _args: list[Arg] = [] + _ctx = self._select("withUp", _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_address(self) -> str: + """Only applies to arguments of type Container. If the argument is not + set, load it from the given address (e.g. alpine:latest) + + 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("defaultAddress", _args) + return await _ctx.execute(str) + + 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 deprecated(self) -> str | None: + """The reason this function is deprecated, if any. + + 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("deprecated", _args) + return await _ctx.execute(str | None) + + 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) -> str: + """A unique identifier for this FunctionArg. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) -> str: + """A unique identifier for this FunctionCall. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) -> str: + """A unique identifier for this FunctionCallArgValue. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) -> str: + """A unique identifier for this GeneratedCode. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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 Generator(Type): + def changes(self) -> Changeset: + """The generated changeset from the last run""" + _args: list[Arg] = [] + _ctx = self._select("changes", _args) + return Changeset(_ctx) + + async def completed(self) -> bool: + """Whether the generator complete + + 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("completed", _args) + return await _ctx.execute(bool) + + async def description(self) -> str: + """Return the description of the generator + + 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) -> str: + """A unique identifier for this Generator. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def is_empty(self) -> bool: + """Whether changeset from the last generator run is empty or not + + 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("isEmpty", _args) + return await _ctx.execute(bool) + + async def name(self) -> str: + """Return the fully qualified name of the generator + + 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 original_module(self) -> "Module": + """The original module in which the generator has been defined""" + _args: list[Arg] = [] + _ctx = self._select("originalModule", _args) + return Module(_ctx) + + async def path(self) -> list[str]: + """The path of the generator within its module + + 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("path", _args) + return await _ctx.execute(list[str]) + + def run(self) -> Self: + """Execute the generator""" + _args: list[Arg] = [] + _ctx = self._select("run", _args) + return Generator(_ctx) + + def with_(self, cb: Callable[["Generator"], "Generator"]) -> "Generator": + """Call the provided callable with current Generator. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + +@typecheck +class GeneratorGroup(Type): + def changes( + self, + *, + on_conflict: ChangesetsMergeConflict + | None = ChangesetsMergeConflict.FAIL_EARLY, + ) -> Changeset: + """The combined changes from the last run of the generators + + If any conflict occurs, for instance if the same file is modified by + multiple generators, or if a file is both modified and deleted, an + error is raised and the merge of the changesets will failed. + + Set 'continueOnConflicts' flag to force to merge the changes in a + 'last write wins' strategy. + + Parameters + ---------- + on_conflict: + Strategy to apply on conflicts between generators + """ + _args = [ + Arg("onConflict", on_conflict, ChangesetsMergeConflict.FAIL_EARLY), + ] + _ctx = self._select("changes", _args) + return Changeset(_ctx) + + async def id(self) -> str: + """A unique identifier for this GeneratorGroup. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def is_empty(self) -> bool: + """Whether the generated changeset from the last run is empty or not + + 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("isEmpty", _args) + return await _ctx.execute(bool) + + async def list_(self) -> list[Generator]: + """Return a list of individual generators and their details""" + _args: list[Arg] = [] + _ctx = self._select("list", _args) + return await _ctx.execute_object_list(Generator) + + async def load_failures(self) -> list[str]: + """Load failures tolerated while collecting the generators. + + Empty unless a workspace module could not be loaded during an unscoped + 'dagger generate' (no selector), where load failures are tolerated so + the modules that do load still generate. Each entry is a human- + readable error message. An explicit selector keeps failing hard + instead. + + 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("loadFailures", _args) + return await _ctx.execute(list[str]) + + def run(self) -> Self: + """Execute all selected generators""" + _args: list[Arg] = [] + _ctx = self._select("run", _args) + return GeneratorGroup(_ctx) + + def with_( + self, cb: Callable[["GeneratorGroup"], "GeneratorGroup"] + ) -> "GeneratorGroup": + """Call the provided callable with current GeneratorGroup. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + +@typecheck +class GitCommit(Type): + """An immutable git commit.""" + + def ancestor_release_tag( + self, + *, + include_pre_release: bool | None = False, + ) -> "GitRef": + """The latest semver release tag reachable from this commit. + + Parameters + ---------- + include_pre_release: + Include pre-release tags when choosing the latest tag. + """ + _args = [ + Arg("includePreRelease", include_pre_release, False), + ] + _ctx = self._select("ancestorReleaseTag", _args) + return GitRef(_ctx) + + async def author_email(self) -> str: + """Git author email. + + 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("authorEmail", _args) + return await _ctx.execute(str) + + async def author_name(self) -> str: + """Git author 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("authorName", _args) + return await _ctx.execute(str) + + async def authored_date(self) -> str: + """Git author date, in RFC3339 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("authoredDate", _args) + return await _ctx.execute(str) + + async def committed_date(self) -> str: + """Git committer date, in RFC3339 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("committedDate", _args) + return await _ctx.execute(str) + + async def committer_email(self) -> str: + """Git committer email. + + 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("committerEmail", _args) + return await _ctx.execute(str) + + async def committer_name(self) -> str: + """Git committer 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("committerName", _args) + return await _ctx.execute(str) + + async def id(self) -> str: + """A unique identifier for this GitCommit. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def message(self) -> str: + """Full commit message. + + 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 message_body(self) -> str: + """Commit message body, excluding the headline. + + 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("messageBody", _args) + return await _ctx.execute(str) + + async def message_headline(self) -> str: + """First line of the commit message. + + 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("messageHeadline", _args) + return await _ctx.execute(str) + + async def parent_shas(self) -> list[str]: + """Parent commit SHAs. + + 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("parentShas", _args) + return await _ctx.execute(list[str]) + + def release_tag( + self, + *, + include_pre_release: bool | None = False, + ) -> "GitRef": + """The latest semver release tag that points directly at this commit. + + Parameters + ---------- + include_pre_release: + Include pre-release tags when choosing the latest tag. + """ + _args = [ + Arg("includePreRelease", include_pre_release, False), + ] + _ctx = self._select("releaseTag", _args) + return GitRef(_ctx) + + async def sha(self) -> str: + """The full commit SHA. + + 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("sha", _args) + return await _ctx.execute(str) + + async def short_sha(self) -> str: + """The abbreviated commit SHA. + + 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("shortSha", _args) + return await _ctx.execute(str) + + def tree( + self, + *, + discard_git_dir: bool | None = False, + depth: int | None = 1, + include_tags: bool | None = False, + ) -> Directory: + """The filesystem tree at this commit. + + Parameters + ---------- + discard_git_dir: + Set to true to discard .git directory. + depth: + The depth of the tree to fetch. + include_tags: + Set to true to populate tag refs in the local checkout .git. + """ + _args = [ + Arg("discardGitDir", discard_git_dir, False), + Arg("depth", depth, 1), + Arg("includeTags", include_tags, False), + ] + _ctx = self._select("tree", _args) + return Directory(_ctx) + + +@typecheck +class GitRef(Type): + """A git ref (tag, branch, or commit).""" + + def as_workspace(self, *, cwd: str | None = "/") -> "Workspace": + """Creates a synthetic workspace from this git ref. + + Parameters + ---------- + cwd: + Current working directory inside the workspace root. Defaults to + the workspace root. + """ + _args = [ + Arg("cwd", cwd, "/"), + ] + _ctx = self._select("asWorkspace", _args) + return Workspace(_ctx) + + async def commit(self) -> str: + """The resolved commit id at this ref. + + .. deprecated:: + Use "commitSHA" instead. + + 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. + """ + warnings.warn( + 'Method "commit" is deprecated: Use "commitSHA" instead.', + DeprecationWarning, + stacklevel=4, + ) + _args: list[Arg] = [] + _ctx = self._select("commit", _args) + return await _ctx.execute(str) + + async def commit_sha(self) -> str: + """The resolved commit SHA 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("commitSHA", _args) + return await _ctx.execute(str) + + def common_ancestor(self, other: Self) -> Self: + """Find the best common ancestor between this ref and another ref. + + Parameters + ---------- + other: + The other ref to compare against. + """ + _args = [ + Arg("other", other), + ] + _ctx = self._select("commonAncestor", _args) + return GitRef(_ctx) + + async def id(self) -> str: + """A unique identifier for this GitRef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def log( + self, + *, + limit: int | None = 10, + paths: list[str] | None = None, + base: "GitRef | None" = None, + ) -> list[GitCommit]: + """Commits reachable from this ref, newest first, starting with the + commit this ref resolves to. + + Parameters + ---------- + limit: + Maximum number of commits to return. + paths: + Only include commits touching these paths, relative to the root of + the repository. + base: + Exclude commits reachable from this ref, i.e. only list commits + added on top of it. + """ + _args = [ + Arg("limit", limit, 10), + Arg("paths", paths, None), + Arg("base", base, None), + ] + _ctx = self._select("log", _args) + return await _ctx.execute_object_list(GitCommit) + + async def name(self) -> str: + """The resolved name of 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("name", _args) + return await _ctx.execute(str) + + async def ref(self) -> str: + """The resolved ref name at this ref. + + .. deprecated:: + Use "name" instead. + + 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. + """ + warnings.warn( + 'Method "ref" is deprecated: Use "name" instead.', + DeprecationWarning, + stacklevel=4, + ) + _args: list[Arg] = [] + _ctx = self._select("ref", _args) + return await _ctx.execute(str) + + def target_commit(self) -> GitCommit: + """The commit this ref resolves to.""" + _args: list[Arg] = [] + _ctx = self._select("targetCommit", _args) + return GitCommit(_ctx) + + def tree( + self, + *, + discard_git_dir: bool | None = False, + depth: int | None = 1, + include_tags: bool | None = False, + ) -> 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. + include_tags: + Set to true to populate tag refs in the local checkout .git. + """ + _args = [ + Arg("discardGitDir", discard_git_dir, False), + Arg("depth", depth, 1), + Arg("includeTags", include_tags, False), + ] + _ctx = self._select("tree", _args) + return Directory(_ctx) + + def with_(self, cb: Callable[["GitRef"], "GitRef"]) -> "GitRef": + """Call the provided callable with current GitRef. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + +@typecheck +class GitRepository(Type): + """A git repository.""" + + def as_workspace(self, *, cwd: str | None = "/") -> "Workspace": + """Creates a synthetic workspace from this git repository. + + Parameters + ---------- + cwd: + Current working directory inside the workspace root. Defaults to + the workspace root. + """ + _args = [ + Arg("cwd", cwd, "/"), + ] + _ctx = self._select("asWorkspace", _args) + return Workspace(_ctx) + + 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) -> GitCommit: + """Returns details of a commit. + + Parameters + ---------- + id: + Identifier of the commit (e.g., + "b6315d8f2810962c601af73f86831f6866ea798b"). + """ + _args = [ + Arg("id", id), + ] + _ctx = self._select("commit", _args) + return GitCommit(_ctx) + + def head(self) -> GitRef: + """Returns details for HEAD.""" + _args: list[Arg] = [] + _ctx = self._select("head", _args) + return GitRef(_ctx) + + async def id(self) -> str: + """A unique identifier for this GitRepository. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + def latest_version(self) -> GitRef: + """Returns details for the latest semver tag.""" + _args: list[Arg] = [] + _ctx = self._select("latestVersion", _args) + return GitRef(_ctx) + + 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 uncommitted(self) -> Changeset: + """Returns the changeset of uncommitted changes in the git repository.""" + _args: list[Arg] = [] + _ctx = self._select("uncommitted", _args) + return Changeset(_ctx) + + async def url(self) -> str | None: + """The URL of the git repository. + + 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("url", _args) + return await _ctx.execute(str | None) + + +@typecheck +class HTTPState(Type): + """An internal persistent HTTP state.""" + + async def id(self) -> str: + """A unique identifier for this HTTPState. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + +@typecheck +class HealthcheckConfig(Type): + """Image healthcheck configuration.""" + + async def args(self) -> list[str]: + """Healthcheck command 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("args", _args) + return await _ctx.execute(list[str]) + + async def id(self) -> str: + """A unique identifier for this HealthcheckConfig. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def interval(self) -> str: + """Interval between running healthcheck. Example:30s + + 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("interval", _args) + return await _ctx.execute(str) + + async def retries(self) -> int: + """The maximum number of consecutive failures before the container is + marked as unhealthy. Example:3 + + 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("retries", _args) + return await _ctx.execute(int) + + async def shell(self) -> bool: + """Healthcheck command is a shell command. + + 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("shell", _args) + return await _ctx.execute(bool) + + async def start_interval(self) -> str: + """StartInterval configures the duration between checks during the + startup phase. Example:5s + + 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("startInterval", _args) + return await _ctx.execute(str) + + async def start_period(self) -> str: + """StartPeriod allows for failures during this initial startup period + which do not count towards maximum number of retries. Example:0s + + 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("startPeriod", _args) + return await _ctx.execute(str) + + async def timeout(self) -> str: + """Healthcheck timeout. Example:3s + + 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("timeout", _args) + return await _ctx.execute(str) + + +@typecheck +class Host(Type): + """Information about the host environment.""" + + def container_image(self, name: str) -> Container: + """Accesses a container image on the host. + + Parameters + ---------- + name: + Name of the image to access. + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("containerImage", _args) + return Container(_ctx) + + def directory( + self, + path: str, + *, + exclude: list[str] | None = None, + include: list[str] | None = None, + no_cache: bool | None = False, + gitignore: bool | None = False, + ) -> Directory: + """Accesses a directory on the host. + + 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.*"]). + no_cache: + If true, the directory will always be reloaded from the host. + gitignore: + Apply .gitignore filter rules inside the directory + """ + _args = [ + Arg("path", path), + Arg("exclude", [] if exclude is None else exclude, []), + Arg("include", [] if include is None else include, []), + Arg("noCache", no_cache, False), + Arg("gitignore", gitignore, False), + ] + _ctx = self._select("directory", _args) + return Directory(_ctx) + + def file( + self, + path: str, + *, + no_cache: bool | None = False, + ) -> File: + """Accesses a file on the host. + + Parameters + ---------- + path: + Location of the file to retrieve (e.g., "README.md"). + no_cache: + If true, the file will always be reloaded from the host. + """ + _args = [ + Arg("path", path), + Arg("noCache", no_cache, False), + ] + _ctx = self._select("file", _args) + return File(_ctx) + + async def find_up( + self, + name: str, + *, + no_cache: bool | None = False, + ) -> str | None: + """Search for a file or directory by walking up the tree from system + workdir. Return its relative path. If no match, return null + + Parameters + ---------- + name: + name of the file or directory to search for + no_cache: + + 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), + Arg("noCache", no_cache, False), + ] + _ctx = self._select("findUp", _args) + return await _ctx.execute(str | None) + + async def id(self) -> str: + """A unique identifier for this Host. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + def service( + self, + ports: list[PortForward], + *, + host: str | None = "localhost", + ) -> "Service": + """Creates a service that forwards traffic to a specified address via the + host. + + Parameters + ---------- + ports: + Ports to expose via the service, forwarding through the host + network. + If a port's frontend is unspecified or 0, it defaults to the same + as the backend port. + An empty set of ports is not valid; an error will be returned. + host: + Upstream host to forward traffic to. + """ + _args = [ + Arg("ports", ports), + Arg("host", host, "localhost"), + ] + _ctx = self._select("service", _args) + return Service(_ctx) + + def tunnel( + self, + service: "Service", + *, + native: bool | None = False, + ports: list[PortForward] | None = None, + ) -> "Service": + """Creates a tunnel that forwards traffic from the host to a service. + + Parameters + ---------- + service: + Service to send traffic from the tunnel. + native: + Map each service port to the same port on the host, as if the + service were running natively. + Note: enabling may result in port conflicts. + ports: + Configure explicit port forwarding rules for the tunnel. + If a port's frontend is unspecified or 0, a random port will be + chosen by the host. + If no ports are given, all of the service's ports are forwarded. + If native is true, each port maps to the same port on the host. If + native is false, each port maps to a random port chosen by the + host. + If ports are given and native is true, the ports are additive. + """ + _args = [ + Arg("service", service), + Arg("native", native, False), + Arg("ports", [] if ports is None else ports, []), + ] + _ctx = self._select("tunnel", _args) + return Service(_ctx) + + def unix_socket(self, path: str) -> "Socket": + """Accesses a Unix socket on the host. + + Parameters + ---------- + path: + Location of the Unix socket (e.g., "/var/run/docker.sock"). + """ + _args = [ + Arg("path", path), + ] + _ctx = self._select("unixSocket", _args) + return Socket(_ctx) + + +@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) -> str: + """A unique identifier for this InputTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) -> str: + """A unique identifier for this InterfaceTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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 JSONValue(Type): + async def as_array(self) -> list["JSONValue"]: + """Decode an array from json""" + _args: list[Arg] = [] + _ctx = self._select("asArray", _args) + return await _ctx.execute_object_list(JSONValue) + + async def as_boolean(self) -> bool: + """Decode a boolean from json + + 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("asBoolean", _args) + return await _ctx.execute(bool) + + async def as_integer(self) -> int: + """Decode an integer from json + + 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("asInteger", _args) + return await _ctx.execute(int) + + async def as_string(self) -> str: + """Decode a string from 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("asString", _args) + return await _ctx.execute(str) + + async def contents( + self, + *, + pretty: bool | None = False, + indent: str | None = " ", + ) -> JSON: + """Return the value encoded as json + + Parameters + ---------- + pretty: + Pretty-print + indent: + Optional line prefix + + 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 = [ + Arg("pretty", pretty, False), + Arg("indent", indent, " "), + ] + _ctx = self._select("contents", _args) + return await _ctx.execute(JSON) + + def field(self, path: list[str]) -> Self: + """Lookup the field at the given path, and return its value. + + Parameters + ---------- + path: + Path of the field to lookup, encoded as an array of field names + """ + _args = [ + Arg("path", path), + ] + _ctx = self._select("field", _args) + return JSONValue(_ctx) + + async def fields(self) -> list[str]: + """List fields of the encoded object + + 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("fields", _args) + return await _ctx.execute(list[str]) + + async def id(self) -> str: + """A unique identifier for this JSONValue. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + def new_boolean(self, value: bool) -> Self: + """Encode a boolean to json + + Parameters + ---------- + value: + New boolean value + """ + _args = [ + Arg("value", value), + ] + _ctx = self._select("newBoolean", _args) + return JSONValue(_ctx) + + def new_integer(self, value: int) -> Self: + """Encode an integer to json + + Parameters + ---------- + value: + New integer value + """ + _args = [ + Arg("value", value), + ] + _ctx = self._select("newInteger", _args) + return JSONValue(_ctx) + + def new_string(self, value: str) -> Self: + """Encode a string to json + + Parameters + ---------- + value: + New string value + """ + _args = [ + Arg("value", value), + ] + _ctx = self._select("newString", _args) + return JSONValue(_ctx) + + def with_contents(self, contents: JSON) -> Self: + """Return a new json value, decoded from the given content + + Parameters + ---------- + contents: + New JSON-encoded contents + """ + _args = [ + Arg("contents", contents), + ] + _ctx = self._select("withContents", _args) + return JSONValue(_ctx) + + def with_field(self, path: list[str], value: Self) -> Self: + """Set a new field at the given path + + Parameters + ---------- + path: + Path of the field to set, encoded as an array of field names + value: + The new value of the field + """ + _args = [ + Arg("path", path), + Arg("value", value), + ] + _ctx = self._select("withField", _args) + return JSONValue(_ctx) + + def with_(self, cb: Callable[["JSONValue"], "JSONValue"]) -> "JSONValue": + """Call the provided callable with current JSONValue. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + +@typecheck +class LLM(Type): + """A conversation with a large language model (LLM): queue prompts, + expose tools, and step the model until it completes its turn.""" + + async def context_tokens(self) -> int: + """estimated number of tokens currently occupying the context window; + unlike tokenUsage this is not cumulative over the session + + 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("contextTokens", _args) + return await _ctx.execute(int) + + async def context_window(self) -> int | None: + """The model's total context window in tokens, or null if unknown (e.g. a + local or uncatalogued model). + + Returns + ------- + int | None + 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("contextWindow", _args) + return await _ctx.execute(int | None) + + def fork(self, label: str) -> Self: + """Fork the conversation, so that otherwise-identical follow-ups evaluate + independently instead of deduplicating to a single cached result. + + Parameters + ---------- + label: + A label distinguishing this fork from its siblings, e.g. + "attempt-2" when retrying a flaky evaluation. + """ + _args = [ + Arg("label", label), + ] + _ctx = self._select("fork", _args) + return LLM(_ctx) + + async def has_pending(self) -> bool: + """Report whether anything is queued to send to the model: an unsent + prompt or unevaluated tool results. When true, another step will do + work; when false, the turn is complete. + + 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("hasPending", _args) + return await _ctx.execute(bool) + + async def id(self) -> str: + """A unique identifier for this LLM. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def last_reply(self) -> str: + """The text of the model's most recent reply. + + 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, + *, + max_steps: int | None = None, + max_tokens: int | None = None, + ) -> Self: + """Send the queued prompt and step the model against the available tools, + until it ends its turn: a reply with no tool calls and nothing left + queued. + + Parameters + ---------- + max_steps: + Cap the number of steps. The loop fails if the cap is reached + before the model ends its turn. + max_tokens: + Cap the model's output tokens on each step. Defaults to the + model's maximum. + """ + _args = [ + Arg("maxSteps", max_steps, None), + Arg("maxTokens", max_tokens, None), + ] + _ctx = self._select("loop", _args) + return LLM(_ctx) + + async def messages(self) -> list["LLMMessage"]: + """The full message history, as structured messages.""" + _args: list[Arg] = [] + _ctx = self._select("messages", _args) + return await _ctx.execute_object_list(LLMMessage) + + async def model(self) -> str: + """The model the conversation is running against, after resolving any + configured default. + + 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 portable_id(self) -> str: + """A portable, self-contained ID for the conversation that node() can + resolve in any session. Unlike id, which may return an engine-local + runtime handle valid only within the current session, this returns the + recipe form suitable for persisting and later restoring the + conversation. The recipe is flattened: bindings superseded during the + session (workspace overlays recorded by each mutating tool call, and + re-bound toolsets) are dropped, while the current workspace binding — + including any pending, un-exported edits — is preserved. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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("portableID", _args) + return await _ctx.execute(str) + + async def provider(self) -> str: + """The provider serving the model, e.g. "anthropic", "openai", "google", + or "local". + + 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 reasoning_effort(self) -> str: + """The reasoning effort in use, e.g. "low", "medium", or "high". Empty or + "none" when reasoning is disabled. + + 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("reasoningEffort", _args) + return await _ctx.execute(str) + + async def replay(self) -> Self: + """Re-emit telemetry spans for the full message history, so a loaded + conversation displays in the TUI. + + 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, "replay", _args) + + async def skills(self) -> list["LLMSkill"]: + """The skills visible to the model, exactly as the ListSkills tool serves + them: engine-embedded skills, skills installed with withSkills, and + skills discovered in the workspace. + """ + _args: list[Arg] = [] + _ctx = self._select("skills", _args) + return await _ctx.execute_object_list(LLMSkill) + + def step(self, *, max_tokens: int | None = None) -> Self: + """Advance the conversation by a single step: send the queued prompt or + tool results to the model, evaluate any tool calls it makes, and queue + their results. Use loop to step until the model ends its turn. + + Parameters + ---------- + max_tokens: + Cap the model's output tokens for this step. Defaults to the + model's maximum. + """ + _args = [ + Arg("maxTokens", max_tokens, None), + ] + _ctx = self._select("step", _args) + return LLM(_ctx) + + async def sync(self) -> Self: + """Force evaluation of the conversation's pending operations (prompts, + steps, loops) 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 token_usage(self) -> "LLMTokenUsage": + """The cumulative token usage, summed across every API call in the + conversation. + """ + _args: list[Arg] = [] + _ctx = self._select("tokenUsage", _args) + return LLMTokenUsage(_ctx) + + async def tools(self) -> str: + """Render documentation for the tools currently exposed to the model. + + 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) + + async def transcript(self) -> str: + """The message history rendered as a plain-text transcript, suitable for + feeding back to an LLM (e.g. for summarization). + + 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("transcript", _args) + return await _ctx.execute(str) + + def with_mcp_server(self, name: str, service: "Service") -> Self: + """Add an external MCP server to the LLM + + Parameters + ---------- + name: + The name of the MCP server + service: + The MCP service to run and communicate with over stdio + """ + _args = [ + Arg("name", name), + Arg("service", service), + ] + _ctx = self._select("withMCPServer", _args) + return LLM(_ctx) + + def with_model( + self, + model: str, + *, + provider: str | None = None, + ) -> Self: + """Change the model for the rest of the conversation. The message history + is preserved; the new model takes effect on the next step. + + Parameters + ---------- + model: + The model to use, e.g. "claude-sonnet-4-5" or "gpt-5.4". + provider: + The provider serving the model, e.g. "openai". Overrides the + provider otherwise inferred from the model name — useful when the + name matches no known pattern (e.g. a fine-tune), or matches the + wrong one. + """ + _args = [ + Arg("model", model), + Arg("provider", provider, None), + ] + _ctx = self._select("withModel", _args) + return LLM(_ctx) + + def with_prompt(self, prompt: str) -> Self: + """Queue a user prompt, to be sent to the model on the next step or loop. + + 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: + """Queue a file's contents as a user prompt, like withPrompt. + + Parameters + ---------- + file: + The file to read the prompt from + """ + _args = [ + Arg("file", file), + ] + _ctx = self._select("withPromptFile", _args) + return LLM(_ctx) + + def with_reasoning_effort(self, effort: str) -> Self: + """Change the reasoning effort for the rest of the conversation, + overriding any configured default. The message history is preserved; + the new effort takes effect on the next step. + + Parameters + ---------- + effort: + The reasoning effort, e.g. "low", "medium", or "high"; "none" + disables reasoning. Supported levels are model-specific — some + models also accept e.g. "minimal", "xhigh", or "max". + """ + _args = [ + Arg("effort", effort), + ] + _ctx = self._select("withReasoningEffort", _args) + return LLM(_ctx) + + def with_response( + self, + content: list[LLMContentBlockInput], + *, + input_tokens: int | None = 0, + output_tokens: int | None = 0, + cached_token_reads: int | None = 0, + cached_token_writes: int | None = 0, + total_tokens: int | None = 0, + ) -> Self: + """Append an assistant response to the message history without calling + the model, e.g. to reconstruct a conversation from another source. + + Parameters + ---------- + content: + The response content + input_tokens: + Uncached input tokens sent + output_tokens: + Tokens received from the model, including text and tool calls + cached_token_reads: + Cached input tokens read + cached_token_writes: + Cached input tokens written + total_tokens: + Total tokens consumed by this response + """ + _args = [ + Arg("content", content), + Arg("inputTokens", input_tokens, 0), + Arg("outputTokens", output_tokens, 0), + Arg("cachedTokenReads", cached_token_reads, 0), + Arg("cachedTokenWrites", cached_token_writes, 0), + Arg("totalTokens", total_tokens, 0), + ] + _ctx = self._select("withResponse", _args) + return LLM(_ctx) + + def with_skills(self, directory: Directory) -> Self: + """Install skills from a directory, adding them to the skills the model + discovers with ListSkills and reads with ReadSkill. Each skill is a + directory containing a SKILL.md with name and description frontmatter, + discovered anywhere in the tree. Installed skills take precedence over + skills discovered in the workspace, but cannot shadow the engine's + built-in skills. + + Parameters + ---------- + directory: + A directory containing skills, each a subdirectory holding a + SKILL.md. + """ + _args = [ + Arg("directory", directory), + ] + _ctx = self._select("withSkills", _args) + return LLM(_ctx) + + def with_system_prompt(self, prompt: str) -> Self: + """Add a system prompt, instructing the model across the whole + conversation. + + Parameters + ---------- + prompt: + The system prompt to send + """ + _args = [ + Arg("prompt", prompt), + ] + _ctx = self._select("withSystemPrompt", _args) + return LLM(_ctx) + + def with_tool_result( + self, + call_id: str, + content: str, + errored: bool, + ) -> Self: + """Append the result of a tool call to the message history. + + Parameters + ---------- + call_id: + The ID of the tool call this result responds to + content: + The content returned by the tool + errored: + Whether the tool call resulted in an error + """ + _args = [ + Arg("callId", call_id), + Arg("content", content), + Arg("errored", errored), + ] + _ctx = self._select("withToolResult", _args) + return LLM(_ctx) + + def with_tools( + self, + object: Node, + *, + except_: list[str] | None = None, + ) -> Self: + """Expose an object's methods as tools. Every eligible method of the + bound object becomes a tool; a tool that returns this object's own + type replaces it as the new state. Repeatable to bind several objects. + + Parameters + ---------- + object: + The object whose methods become tools. + except_: + Method names to exclude from the toolset (e.g. constructors, + entrypoints). + """ + _args = [ + Arg("object", object), + Arg("except", [] if except_ is None else except_, []), + ] + _ctx = self._select("withTools", _args) + return LLM(_ctx) + + def with_workspace(self, workspace: "Workspace") -> Self: + """Bind the LLM to a workspace, exposing its modules as tools exactly as + the Dagger CLI would serve them for that workspace. + + Parameters + ---------- + workspace: + The workspace to work in. + """ + _args = [ + Arg("workspace", workspace), + ] + _ctx = self._select("withWorkspace", _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 without_message_history(self) -> Self: + """Clear the message history, keeping only the system prompts.""" + _args: list[Arg] = [] + _ctx = self._select("withoutMessageHistory", _args) + return LLM(_ctx) + + def without_system_prompts(self) -> Self: + """Clear the user-added system prompts, keeping only the default system + prompt. + """ + _args: list[Arg] = [] + _ctx = self._select("withoutSystemPrompts", _args) + return LLM(_ctx) + + def workspace(self) -> "Workspace": + """Return the workspace the LLM is bound to.""" + _args: list[Arg] = [] + _ctx = self._select("workspace", _args) + return Workspace(_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 LLMContentBlock(Type): + """A single piece of content within an LLM message.""" + + async def arguments(self) -> JSON: + """The arguments passed to the tool, JSON-encoded (for TOOL_CALL kind). + + 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("arguments", _args) + return await _ctx.execute(JSON) + + async def call_id(self) -> str: + """The unique ID of a tool call (for TOOL_CALL or TOOL_RESULT kinds). + + 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("callId", _args) + return await _ctx.execute(str) + + async def errored(self) -> bool: + """Whether the tool call resulted in an error (for TOOL_RESULT kind). + + 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("errored", _args) + return await _ctx.execute(bool) + + async def id(self) -> str: + """A unique identifier for this LLMContentBlock. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def kind(self) -> LLMContentBlockKind: + """The kind of content block, which determines the other populated + fields. + + Returns + ------- + LLMContentBlockKind + The kind of content in a message block. + + 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(LLMContentBlockKind) + + async def signature(self) -> str: + """Provider-specific opaque data (e.g. Anthropic thinking signature). + Preserve it when reconstructing a conversation. + + 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("signature", _args) + return await _ctx.execute(str) + + async def text(self) -> str: + """Text content (for TEXT, THINKING, or TOOL_RESULT kinds). + + 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("text", _args) + return await _ctx.execute(str) + + async def tool_name(self) -> str: + """The name of the tool called (for TOOL_CALL kind). + + 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("toolName", _args) + return await _ctx.execute(str) + + +@typecheck +class LLMMessage(Type): + """A single message in an LLM conversation.""" + + async def content(self) -> list[LLMContentBlock]: + """The message's content blocks, in the order the model produced them.""" + _args: list[Arg] = [] + _ctx = self._select("content", _args) + return await _ctx.execute_object_list(LLMContentBlock) + + async def id(self) -> str: + """A unique identifier for this LLMMessage. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def role(self) -> LLMMessageRole: + """The role that produced this message. + + Returns + ------- + LLMMessageRole + The role that generated a message. + + 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("role", _args) + return await _ctx.execute(LLMMessageRole) + + def token_usage(self) -> "LLMTokenUsage": + """Token usage reported by the provider for the API call that produced + this message; all zeros except on assistant responses. + """ + _args: list[Arg] = [] + _ctx = self._select("tokenUsage", _args) + return LLMTokenUsage(_ctx) + + +@typecheck +class LLMSkill(Type): + """A skill available to a model: task-specific guidance discovered + with ListSkills and read with ReadSkill.""" + + async def description(self) -> str: + """The one-line description from the SKILL.md frontmatter. + + 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) -> str: + """A unique identifier for this LLMSkill. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def name(self) -> str: + """The skill name, as passed to ReadSkill. + + 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 LLMTokenUsage(Type): + """A count of tokens consumed by LLM API calls.""" + + async def cached_token_reads(self) -> int: + """Input tokens served from the provider's prompt cache. + + 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: + """Input tokens written to the provider's prompt cache. + + 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) -> str: + """A unique identifier for this LLMTokenUsage. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def input_tokens(self) -> int: + """Uncached input tokens sent to the model. + + 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: + """Tokens received from the model, including text and tool calls. + + 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: + """Total tokens consumed, as reported by the provider. + + 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) -> str: + """A unique identifier for this Label. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) -> str: + """A unique identifier for this ListTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + +@typecheck +class Module(Type): + """A Dagger module.""" + + def check(self, name: str) -> Check: + """Return the check defined by the module with the given name. Must match + to exactly one check. + + .. caution:: + Experimental: This API is highly experimental and may be removed + or replaced entirely. + + Parameters + ---------- + name: + The name of the check to retrieve + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("check", _args) + return Check(_ctx) + + def checks( + self, + *, + include: list[str] | None = None, + no_generate: bool | None = None, + ) -> CheckGroup: + """Return all checks defined by the module + + .. caution:: + Experimental: This API is highly experimental and may be removed + or replaced entirely. + + Parameters + ---------- + include: + Only include checks matching the specified patterns + no_generate: + When true, only return annotated check functions; exclude + generate-as-checks + """ + _args = [ + Arg("include", include, None), + Arg("noGenerate", no_generate, None), + ] + _ctx = self._select("checks", _args) + return CheckGroup(_ctx) + + 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) + + def generator(self, name: str) -> Generator: + """Return the generator defined by the module with the given name. Must + match to exactly one generator. + + .. caution:: + Experimental: This API is highly experimental and may be removed + or replaced entirely. + + Parameters + ---------- + name: + The name of the generator to retrieve + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("generator", _args) + return Generator(_ctx) + + def generators( + self, + *, + include: list[str] | None = None, + ) -> GeneratorGroup: + """Return all generators defined by the module + + .. caution:: + Experimental: This API is highly experimental and may be removed + or replaced entirely. + + Parameters + ---------- + include: + Only include generators matching the specified patterns + """ + _args = [ + Arg("include", include, None), + ] + _ctx = self._select("generators", _args) + return GeneratorGroup(_ctx) + + async def id(self) -> str: + """A unique identifier for this Module. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) + + def introspection_schema_json(self) -> File: + """The introspection schema JSON file for this module. + + This file represents the schema visible to the module's source code, + including all core types and those from the dependencies. + + Note: this is in the context of a module, so some core types may be + hidden. + """ + _args: list[Arg] = [] + _ctx = self._select("introspectionSchemaJSON", _args) + return File(_ctx) + + 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, + entrypoint: 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 + entrypoint: + Install the module as the entrypoint, promoting its main-object + methods onto the Query root + + 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), + Arg("entrypoint", entrypoint, None), + ] + _ctx = self._select("serve", _args) + await _ctx.execute() + + def services( + self, + *, + include: list[str] | None = None, + ) -> "UpGroup": + """Return all services defined by the module + + .. caution:: + Experimental: This API is highly experimental and may be removed + or replaced entirely. + + Parameters + ---------- + include: + Only include services matching the specified patterns + """ + _args = [ + Arg("include", include, None), + ] + _ctx = self._select("services", _args) + return UpGroup(_ctx) + + 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 user_defaults(self) -> EnvFile: + """User-defined default values, loaded from local .env files.""" + _args: list[Arg] = [] + _ctx = self._select("userDefaults", _args) + return EnvFile(_ctx) + + 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) -> str: + """A unique identifier for this ModuleConfigClient. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + +@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) + + def blueprint(self) -> Self: + """The blueprint referenced by the module source. + + .. deprecated:: + Legacy dagger.json field. Generic module loading no longer honors + it; use workspace modules in dagger.toml instead. + """ + warnings.warn( + 'Method "blueprint" is deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in dagger.toml instead.', + DeprecationWarning, + stacklevel=4, + ) + _args: list[Arg] = [] + _ctx = self._select("blueprint", _args) + return ModuleSource(_ctx) + + def client_schema_introspection_json(self) -> File: + """The client-facing introspection schema JSON file for this module + source. + + This is the schema consumed by client codegen: unlike + introspectionSchemaJSON (the module-facing schema), it hides no core + types and installs this module (reached via dag.) so a + generated client can bind it. The module's dependencies are excluded: + a client is generated for a single module plus core, not its + dependency graph. + """ + _args: list[Arg] = [] + _ctx = self._select("clientSchemaIntrospectionJSON", _args) + return File(_ctx) + + 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 module config file 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 generate(self, workspace: "Workspace") -> "Workspace": + """Return the supplied workspace with this module's generated context + applied. + + The workspace change baseline is preserved, so a later + Workspace.changes call includes this generation together with any + other edits made by the caller. + + Parameters + ---------- + workspace: + The workspace to apply generated files to. + """ + _args = [ + Arg("workspace", workspace), + ] + _ctx = self._select("generate", _args) + return Workspace(_ctx) + + def generate_local_dependencies(self, workspace: "Workspace") -> Changeset: + """Generate this module's transitive local dependency closure and return + the staged changes as a single changeset against the unstaged + workspace root. + + Each local dependency is generated by its own SDK against a workspace + scoped to it, carrying the dependency's own already-generated + dependencies. Remote (git) dependencies are assumed committed and + skipped. Overlay the result onto the workspace before generating this + module; it is not this module's own generated code. + + Parameters + ---------- + workspace: + The workspace to generate the local dependencies against. + """ + _args = [ + Arg("workspace", workspace), + ] + _ctx = self._select("generateLocalDependencies", _args) + return Changeset(_ctx) + + def generated_context_changeset(self) -> Changeset: + """The generated files and directories made on top of the module source's + context directory, returned as a Changeset. + """ + _args: list[Arg] = [] + _ctx = self._select("generatedContextChangeset", _args) + return Changeset(_ctx) + + 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) -> str: + """A unique identifier for this ModuleSource. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + def introspection_schema_json(self) -> File: + """The introspection schema JSON file for this module source. + + This file represents the schema visible to the module's source code, + including all core types and those from the dependencies. + + Note: this is in the context of a module, so some core types may be + hidden. + """ + _args: list[Arg] = [] + _ctx = self._select("introspectionSchemaJSON", _args) + return File(_ctx) + + 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 config file + (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 + config. + + 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 toolchains(self) -> list["ModuleSource"]: + """The toolchains referenced by the module source. + + .. deprecated:: + Legacy dagger.json field. Generic module loading no longer honors + it; use workspace modules in dagger.toml instead. + """ + warnings.warn( + 'Method "toolchains" is deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in dagger.toml instead.', + DeprecationWarning, + stacklevel=4, + ) + _args: list[Arg] = [] + _ctx = self._select("toolchains", _args) + return await _ctx.execute_object_list(ModuleSource) + + def updated_config_directory(self) -> Directory: + """The module's dagger.json with any in-memory edits from with* APIs + applied, as a diff relative to the source's context directory. + + Unlike generatedContextDirectory, this does not run codegen and does + not validate the engine version against the running engine, so it can + be used to declare an engine requirement newer than the running + engine. Loading or serving such a module still fails at + moduleSource.asModule. + """ + _args: list[Arg] = [] + _ctx = self._select("updatedConfigDirectory", _args) + return Directory(_ctx) + + def user_defaults(self) -> EnvFile: + """User-defined defaults read from local .env files""" + _args: list[Arg] = [] + _ctx = self._select("userDefaults", _args) + return EnvFile(_ctx) + + 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_blueprint(self, blueprint: Self) -> Self: + """Set a blueprint for the module source. + + .. deprecated:: + Legacy dagger.json field. Generic module loading no longer honors + it; use workspace modules in `dagger.toml` instead. + + Parameters + ---------- + blueprint: + The blueprint module to set. + """ + warnings.warn( + 'Method "with_blueprint" is deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in `dagger.toml` instead.', + DeprecationWarning, + stacklevel=4, + ) + _args = [ + Arg("blueprint", blueprint), + ] + _ctx = self._select("withBlueprint", _args) + return ModuleSource(_ctx) + + 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_experimental_features( + self, + features: list[ModuleSourceExperimentalFeature], + ) -> Self: + """Enable the experimental features for the module source. + + Parameters + ---------- + features: + The experimental features to enable. + """ + _args = [ + Arg("features", features), + ] + _ctx = self._select("withExperimentalFeatures", _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_toolchains(self, toolchains: list["ModuleSource"]) -> Self: + """Add toolchains to the module source. + + .. deprecated:: + Legacy dagger.json field. Generic module loading no longer honors + it; use workspace modules in `dagger.toml` instead. + + Parameters + ---------- + toolchains: + The toolchain modules to add. + """ + warnings.warn( + 'Method "with_toolchains" is deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in `dagger.toml` instead.', + DeprecationWarning, + stacklevel=4, + ) + _args = [ + Arg("toolchains", toolchains), + ] + _ctx = self._select("withToolchains", _args) + return ModuleSource(_ctx) + + def with_update_blueprint(self) -> Self: + """Update the blueprint module to the latest version. + + .. deprecated:: + Legacy dagger.json field. Generic module loading no longer honors + it; use workspace modules in `dagger.toml` instead. + """ + warnings.warn( + 'Method "with_update_blueprint" is deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in `dagger.toml` instead.', + DeprecationWarning, + stacklevel=4, + ) + _args: list[Arg] = [] + _ctx = self._select("withUpdateBlueprint", _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 with_update_toolchains(self, toolchains: list[str]) -> Self: + """Update one or more toolchains. + + .. deprecated:: + Legacy dagger.json field. Generic module loading no longer honors + it; use workspace modules in `dagger.toml` instead. + + Parameters + ---------- + toolchains: + The toolchains to update. + """ + warnings.warn( + 'Method "with_update_toolchains" is deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in `dagger.toml` instead.', + DeprecationWarning, + stacklevel=4, + ) + _args = [ + Arg("toolchains", toolchains), + ] + _ctx = self._select("withUpdateToolchains", _args) + return ModuleSource(_ctx) + + def with_updated_clients(self, clients: list[str]) -> Self: + """Update one or more clients. + + Parameters + ---------- + clients: + The clients to update + """ + _args = [ + Arg("clients", clients), + ] + _ctx = self._select("withUpdatedClients", _args) + return ModuleSource(_ctx) + + def without_blueprint(self) -> Self: + """Remove the current blueprint from the module source. + + .. deprecated:: + Legacy dagger.json field. Generic module loading no longer honors + it; use workspace modules in `dagger.toml` instead. + """ + warnings.warn( + 'Method "without_blueprint" is deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in `dagger.toml` instead.', + DeprecationWarning, + stacklevel=4, + ) + _args: list[Arg] = [] + _ctx = self._select("withoutBlueprint", _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 without_experimental_features( + self, + features: list[ModuleSourceExperimentalFeature], + ) -> Self: + """Disable experimental features for the module source. + + Parameters + ---------- + features: + The experimental features to disable. + """ + _args = [ + Arg("features", features), + ] + _ctx = self._select("withoutExperimentalFeatures", _args) + return ModuleSource(_ctx) + + def without_toolchains(self, toolchains: list[str]) -> Self: + """Remove the provided toolchains from the module source. + + .. deprecated:: + Legacy dagger.json field. Generic module loading no longer honors + it; use workspace modules in `dagger.toml` instead. + + Parameters + ---------- + toolchains: + The toolchains to remove. + """ + warnings.warn( + 'Method "without_toolchains" is deprecated: Legacy dagger.json field. Generic module loading no longer honors it; use workspace modules in `dagger.toml` instead.', + DeprecationWarning, + stacklevel=4, + ) + _args = [ + Arg("toolchains", toolchains), + ] + _ctx = self._select("withoutToolchains", _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 deprecated(self) -> str | None: + """The reason this enum member is deprecated, if any. + + 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("deprecated", _args) + return await _ctx.execute(str | None) + + 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) -> str: + """A unique identifier for this ObjectTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) -> str: + """A unique identifier for this Port. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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 Query(Root): + """The root of the DAG.""" + + def address(self, value: str) -> Address: + """initialize an address to load directories, containers, secrets or + other object types. + """ + _args = [ + Arg("value", value), + ] + _ctx = self._select("address", _args) + return Address(_ctx) + + def cache_volume( + self, + key: str, + *, + source: Directory | None = None, + sharing: CacheSharingMode | None = CacheSharingMode.SHARED, + owner: str | None = "", + ) -> CacheVolume: + """Constructs a cache volume for a given cache key. + + Parameters + ---------- + key: + A string identifier to target this cache volume (e.g., "modules- + cache"). + 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 cache volume root. + 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. + """ + _args = [ + Arg("key", key), + Arg("source", source, None), + Arg("sharing", sharing, CacheSharingMode.SHARED), + Arg("owner", owner, ""), + ] + _ctx = self._select("cacheVolume", _args) + return CacheVolume(_ctx) + + def changeset(self) -> Changeset: + """Creates an empty changeset""" + _args: list[Arg] = [] + _ctx = self._select("changeset", _args) + return Changeset(_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) + + def current_node(self) -> Node: + """The object that received the current module function call, as a Node. + Errors when there is no current call, or the call is top-level (e.g. a + module constructor). + """ + _args: list[Arg] = [] + _ctx = self._select("currentNode", _args) + return _NodeClient(_ctx) + + async def current_type_defs( + self, + *, + return_all_types: bool | None = False, + hide_core: bool | None = None, + ) -> list["TypeDef"]: + """The TypeDef representations of the objects currently being served in + the session. + + Parameters + ---------- + return_all_types: + Return the full referenced typedef closure instead of only top- + level served typedefs. + hide_core: + Strip core API functions from the Query type, leaving only module- + sourced functions (constructors, entrypoint proxies, etc.). + Core types (Container, Directory, etc.) are kept so return types + and method chaining still work. + """ + _args = [ + Arg("returnAllTypes", return_all_types, False), + Arg("hideCore", hide_core, None), + ] + _ctx = self._select("currentTypeDefs", _args) + return await _ctx.execute_object_list(TypeDef) + + def current_workspace(self) -> "Workspace": + """Detect and return the current workspace. + + .. caution:: + Experimental: Highly experimental API extracted from a more + ambitious workspace implementation. + """ + _args: list[Arg] = [] + _ctx = self._select("currentWorkspace", _args) + return Workspace(_ctx) + + 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 engine(self) -> Engine: + """The Dagger engine container configuration and state""" + _args: list[Arg] = [] + _ctx = self._select("engine", _args) + return Engine(_ctx) + + def engine_volume( + self, + name: str, + *, + subdir: str | None = None, + ) -> "Volume": + """Constructs an engine-managed volume backed by operator-provided + storage beneath the configured engine state root. + + Parameters + ---------- + name: + Canonical slash-separated volume name beneath the engine volume + namespace. + subdir: + Optional existing subdirectory within the volume payload to mount. + """ + _args = [ + Arg("name", name), + Arg("subdir", subdir, None), + ] + _ctx = self._select("engineVolume", _args) + return Volume(_ctx) + + def env_file(self, *, expand: bool | None = None) -> EnvFile: + """Initialize an environment file + + Parameters + ---------- + expand: + Replace "${VAR}" or "$VAR" with the value of other vars + .. deprecated:: Variable expansion is now enabled by default + """ + _args = [ + Arg("expand", expand, None), + ] + _ctx = self._select("envFile", _args) + return EnvFile(_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. + .. 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 host(self) -> Host: + """Queries the host environment.""" + _args: list[Arg] = [] + _ctx = self._select("host", _args) + return Host(_ctx) + + def http( + self, + url: str, + *, + name: str | None = None, + permissions: int | None = None, + checksum: str | 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. + checksum: + Expected digest of the downloaded content (e.g., "sha256:..."). + 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("checksum", checksum, None), + Arg("authHeader", auth_header, None), + Arg("experimentalServiceHost", experimental_service_host, None), + ] + _ctx = self._select("http", _args) + return File(_ctx) + + async def id(self) -> str: + """A unique identifier for this Query. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + def json(self) -> JSONValue: + """Initialize a JSON value""" + _args: list[Arg] = [] + _ctx = self._select("json", _args) + return JSONValue(_ctx) + + def llm( + self, + *, + model: str | None = None, + provider: str | None = None, + ) -> LLM: + """Initialize a new LLM conversation. + + .. caution:: + Experimental: LLM support is not yet stabilized + + Parameters + ---------- + model: + The model to converse with, e.g. "claude-sonnet-4-5" or "gpt-5.4". + Defaults to the configured default model. + provider: + The provider serving the model, e.g. "openai". Overrides the + provider otherwise inferred from the model name — useful when the + name matches no known pattern (e.g. a fine-tune), or matches the + wrong one. + """ + _args = [ + Arg("model", model, None), + Arg("provider", provider, None), + ] + _ctx = self._select("llm", _args) + return LLM(_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 a module config file 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 node(self, id: Type) -> Node: + """Load any object by its ID.""" + _args = [ + Arg("id", id), + ] + _ctx = self._select("node", _args) + return _NodeClient(_ctx) + + def schema(self, json: JSON) -> "Schema": + """Load a GraphQL introspection schema for merging. + + Parameters + ---------- + json: + The introspection schema JSON to load. + """ + _args = [ + Arg("json", json), + ] + _ctx = self._select("schema", _args) + return Schema(_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 sshfs_volume( + self, + endpoint: str, + private_key: "Secret", + *, + known_hosts: "Secret | None" = None, + cache_key: str | None = None, + insecure_skip_host_key_check: bool | None = False, + experimental_service_host: "Service | None" = None, + ) -> "Volume": + """Constructs an SSHFS volume. + + Parameters + ---------- + endpoint: + SSHFS endpoint URL in the form + sshfs://user@host[:port]/absolute/path. + private_key: + Private key secret used to authenticate to the remote host. + known_hosts: + known_hosts material used to verify the remote host key. Required + unless insecureSkipHostKeyCheck is true. + cache_key: + Optional cache equivalence key. If set, volumes with the same + cacheKey may be considered equivalent for cache lookups, still + subject to their resource dependencies. + insecure_skip_host_key_check: + Disable SSH host key verification. This is insecure and must be + explicitly opted into. + experimental_service_host: + Service to use as the SSHFS network endpoint while verifying the + original host key. + """ + _args = [ + Arg("endpoint", endpoint), + Arg("privateKey", private_key), + Arg("knownHosts", known_hosts, None), + Arg("cacheKey", cache_key, None), + Arg("insecureSkipHostKeyCheck", insecure_skip_host_key_check, False), + Arg("experimentalServiceHost", experimental_service_host, None), + ] + _ctx = self._select("sshfsVolume", _args) + return Volume(_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 RemoteGitMirror(Type): + """An internal persistent bare git mirror.""" + + async def id(self) -> str: + """A unique identifier for this RemoteGitMirror. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + +@typecheck +class SDKConfig(Type): + """The SDK config of the module.""" + + async def debug(self) -> bool: + """Whether to start the SDK runtime in debug mode with an interactive + terminal. + + 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("debug", _args) + return await _ctx.execute(bool) + + async def id(self) -> str: + """A unique identifier for this SDKConfig. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) -> str: + """A unique identifier for this ScalarTypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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 Schema(Type): + """A GraphQL introspection schema that can be inspected and merged.""" + + async def contents(self) -> JSON: + """Serialize the schema back to introspection 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("contents", _args) + return await _ctx.execute(JSON) + + async def id(self) -> str: + """A unique identifier for this Schema. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + def merge( + self, + module_types: JSON, + module_name: str, + ) -> Self: + """Merge a module's introspection-shaped type definitions into the + schema, returning the combined schema. + + Parameters + ---------- + module_types: + Introspection JSON describing the types the module defines. + Object, interface and enum types are appended to the schema, and a + constructor field for the module is added to the Query type. + module_name: + The name of the module whose types are being merged. Used to stamp + the @sourceMap directive and to derive the module's constructor + field. + """ + _args = [ + Arg("moduleTypes", module_types), + Arg("moduleName", module_name), + ] + _ctx = self._select("merge", _args) + return Schema(_ctx) + + def with_(self, cb: Callable[["Schema"], "Schema"]) -> "Schema": + """Call the provided callable with current Schema. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + +@typecheck +class SearchResult(Type): + async def absolute_offset(self) -> int: + """The byte offset of this line within the file. + + 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("absoluteOffset", _args) + return await _ctx.execute(int) + + async def file_path(self) -> str: + """The path to the file that matched. + + 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("filePath", _args) + return await _ctx.execute(str) + + async def id(self) -> str: + """A unique identifier for this SearchResult. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def line_number(self) -> int: + """The first line that matched. + + 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("lineNumber", _args) + return await _ctx.execute(int) + + async def matched_lines(self) -> str: + """The line content that matched. + + 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("matchedLines", _args) + return await _ctx.execute(str) + + async def submatches(self) -> list["SearchSubmatch"]: + """Sub-match positions and content within the matched lines.""" + _args: list[Arg] = [] + _ctx = self._select("submatches", _args) + return await _ctx.execute_object_list(SearchSubmatch) + + +@typecheck +class SearchSubmatch(Type): + async def end(self) -> int: + """The match's end offset within the matched lines. + + 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("end", _args) + return await _ctx.execute(int) + + async def id(self) -> str: + """A unique identifier for this SearchSubmatch. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def start(self) -> int: + """The match's start offset within the matched lines. + + 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("start", _args) + return await _ctx.execute(int) + + async def text(self) -> str: + """The matched text. + + 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("text", _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) -> str: + """A unique identifier for this Secret. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) -> str: + """A unique identifier for this Service. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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 sync(self) -> Self: + """Forces evaluation of the pipeline 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, *, cmd: list[str] | None = None) -> Self: + _args = [ + Arg("cmd", [] if cmd is None else cmd, []), + ] + _ctx = self._select("terminal", _args) + return Service(_ctx) + + 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) -> str: + """A unique identifier for this Socket. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + +@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) -> str: + """A unique identifier for this SourceMap. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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) + + async def url(self) -> str: + """The URL to the file, if any. This can be used to link to the source + map in the browser. + + 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("url", _args) + return await _ctx.execute(str) + + +@typecheck +class Stat(Type): + """A file or directory status object.""" + + async def file_type(self) -> FileType | None: + """file type + + Returns + ------- + FileType | None + File type. + + 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("fileType", _args) + return await _ctx.execute(FileType | None) + + async def id(self) -> str: + """A unique identifier for this Stat. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def name(self) -> str: + """file 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 permissions(self) -> int: + """permission bits + + 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("permissions", _args) + return await _ctx.execute(int) + + async def size(self) -> int: + """file size + + 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) + + +@typecheck +class Terminal(Type): + """An interactive terminal that clients can connect to.""" + + async def id(self) -> str: + """A unique identifier for this Terminal. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(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__() + + +@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) -> str: + """A unique identifier for this TypeDef. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + 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 name(self) -> str: + """The canonical non-optional name of the 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("name", _args) + return await _ctx.execute(str) + + 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, + deprecated: str | 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. + deprecated: + If deprecated, the reason or migration path. + """ + _args = [ + Arg("name", name), + Arg("value", value, ""), + Arg("description", description, ""), + Arg("sourceMap", source_map, None), + Arg("deprecated", deprecated, None), + ] + _ctx = self._select("withEnumMember", _args) + return TypeDef(_ctx) + + def with_enum_value( + self, + value: str, + *, + description: str | None = "", + source_map: SourceMap | None = None, + deprecated: str | 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. + deprecated: + If deprecated, the reason or migration path. + """ + 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), + Arg("deprecated", deprecated, 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, + deprecated: str | 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. + deprecated: + If deprecated, the reason or migration path. + """ + _args = [ + Arg("name", name), + Arg("typeDef", type_def), + Arg("description", description, ""), + Arg("sourceMap", source_map, None), + Arg("deprecated", deprecated, 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, + deprecated: str | 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), + Arg("deprecated", deprecated, 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) + + +@typecheck +class Up(Type): + async def description(self) -> str: + """The description of the service + + 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) -> str: + """A unique identifier for this Up. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def name(self) -> str: + """Return the fully qualified name of the service + + 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 original_module(self) -> Module: + """The original module in which the service has been defined""" + _args: list[Arg] = [] + _ctx = self._select("originalModule", _args) + return Module(_ctx) + + async def path(self) -> list[str]: + """The path of the service within its module + + 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("path", _args) + return await _ctx.execute(list[str]) + + def run(self) -> Self: + """Execute the service function""" + _args: list[Arg] = [] + _ctx = self._select("run", _args) + return Up(_ctx) + + def with_(self, cb: Callable[["Up"], "Up"]) -> "Up": + """Call the provided callable with current Up. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + +@typecheck +class UpGroup(Type): + async def id(self) -> str: + """A unique identifier for this UpGroup. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def list_(self) -> list[Up]: + """Return a list of individual services and their details""" + _args: list[Arg] = [] + _ctx = self._select("list", _args) + return await _ctx.execute_object_list(Up) + + def run(self) -> Self: + """Execute all selected service functions""" + _args: list[Arg] = [] + _ctx = self._select("run", _args) + return UpGroup(_ctx) + + def with_(self, cb: Callable[["UpGroup"], "UpGroup"]) -> "UpGroup": + """Call the provided callable with current UpGroup. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + +@typecheck +class Volume(Type): + """A filesystem volume that can be mounted into containers.""" + + async def id(self) -> str: + """A unique identifier for this Volume. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + +@typecheck +class Workspace(Type): + """A Dagger workspace detected from the current working directory or + constructed from a Directory.""" + + async def address(self) -> str: + """Canonical Dagger address of the workspace location, or an opaque + identity for synthetic workspaces. + + 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("address", _args) + return await _ctx.execute(str) + + def agents( + self, + *, + include: list[str] | None = None, + ) -> AgentGroup: + """Return all agent middlewares from modules loaded in the workspace. + + Parameters + ---------- + include: + Only include agents matching the specified patterns + """ + _args = [ + Arg("include", include, None), + ] + _ctx = self._select("agents", _args) + return AgentGroup(_ctx) + + def changes(self, *, from_: "Workspace | None" = None) -> Changeset: + """Return this workspace's changes, with paths relative to its working + directory. + + Pass from to compare against an earlier workspace state. Omitting it + preserves the cumulative behavior used by clients from before this + argument was added. + + Parameters + ---------- + from_: + An earlier workspace state to compare against. + """ + _args = [ + Arg("from", from_, None), + ] + _ctx = self._select("changes", _args) + return Changeset(_ctx) + + def checks( + self, + *, + include: list[str] | None = None, + skip: list[str] | None = None, + no_generate: bool | None = None, + only_generate: bool | None = None, + ) -> CheckGroup: + """Return all checks from modules loaded in the workspace. + + Parameters + ---------- + include: + Only include checks matching the specified patterns + skip: + Skip checks matching the specified patterns + no_generate: + When true, only return annotated check functions; exclude + generate-as-checks + only_generate: + When true, only return generate-as-checks; exclude annotated check + functions + """ + _args = [ + Arg("include", include, None), + Arg("skip", skip, None), + Arg("noGenerate", no_generate, None), + Arg("onlyGenerate", only_generate, None), + ] + _ctx = self._select("checks", _args) + return CheckGroup(_ctx) + + async def config_file(self) -> str: + """Selected native workspace config file relative to the workspace cwd, + 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("configFile", _args) + return await _ctx.execute(str) + + async def config_read(self, *, key: str | None = "") -> str: + """Read a configuration value from dagger.toml. + + If key is empty, returns the full config. + + If key points to a scalar, returns the value. + + If key points to a table, returns flattened dotted-key output. + + Parameters + ---------- + key: + Dotted key path (e.g. modules.greeter.source). Empty for full + config. + + 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("key", key, ""), + ] + _ctx = self._select("configRead", _args) + return await _ctx.execute(str) + + async def cwd(self) -> str: + """Current location within the workspace root. + + The workspace root is returned as "/". + + Relative paths in workspace APIs resolve from here. + + 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("cwd", _args) + return await _ctx.execute(str) + + def directory( + self, + path: str, + *, + exclude: list[str] | None = None, + include: list[str] | None = None, + gitignore: bool | None = False, + ) -> Directory: + """Returns a Directory from the workspace. + + Relative paths resolve from the workspace cwd. Absolute paths resolve + from the workspace root. + + Parameters + ---------- + path: + Location of the directory to retrieve. Relative paths (e.g., + "src") resolve from the workspace cwd; absolute paths (e.g., + "/src") resolve from the workspace root. + 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.*"]). + gitignore: + Apply .gitignore filter rules inside the directory. + """ + _args = [ + Arg("path", path), + Arg("exclude", [] if exclude is None else exclude, []), + Arg("include", [] if include is None else include, []), + Arg("gitignore", gitignore, False), + ] + _ctx = self._select("directory", _args) + return Directory(_ctx) + + async def env_list(self) -> list[str]: + """List named environments defined in the workspace configuration. + + 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("envList", _args) + return await _ctx.execute(list[str]) + + async def export(self) -> Void: + """Write this workspace's pending changes to its local Git workspace. + + Returns + ------- + Void + 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: list[Arg] = [] + _ctx = self._select("export", _args) + await _ctx.execute() + + def file(self, path: str) -> File: + """Returns a File from the workspace. + + Relative paths resolve from the workspace cwd. Absolute paths resolve + from the workspace root. + + Parameters + ---------- + path: + Location of the file to retrieve. Relative paths (e.g., "go.mod") + resolve from the workspace cwd; absolute paths (e.g., "/go.mod") + resolve from the workspace root. + """ + _args = [ + Arg("path", path), + ] + _ctx = self._select("file", _args) + return File(_ctx) + + async def find_roots( + self, + markers: list[str], + *, + start: str | None = ".", + exclude: list[str] | None = None, + ) -> list[str]: + """Find project roots marked by any of the given filenames, starting from + a path relative to the workspace cwd. + + Returns cwd-relative directory paths for every marked directory at or + below start, plus the nearest marked ancestor when start itself is not + marked. + + Each returned path is usable as-is with other workspace APIs, e.g. + directory(path). + + Parameters + ---------- + markers: + File basenames that mark a project root (e.g. ["go.mod"] or + ["deno.json", "deno.jsonc"]). + start: + Directory to start from. Relative paths resolve from the workspace + cwd. + exclude: + Glob patterns pruning the walk below start (e.g. + ["**/node_modules/**"]). + + 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("markers", markers), + Arg("start", start, "."), + Arg("exclude", [] if exclude is None else exclude, []), + ] + _ctx = self._select("findRoots", _args) + return await _ctx.execute(list[str]) + + async def find_up( + self, + name: str, + *, + from_: str | None = ".", + ) -> str | None: + """Search for a file or directory by walking up from the start path + within the workspace. + + Returns the absolute workspace path if found, or null if not found. + + Relative start paths resolve from the workspace cwd. + + The search stops at the workspace root and will not traverse above it. + + Parameters + ---------- + name: + The name of the file or directory to search for. + from_: + Path to start the search from. Relative paths resolve from the + workspace cwd; absolute paths resolve from the workspace root. + + 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), + Arg("from", from_, "."), + ] + _ctx = self._select("findUp", _args) + return await _ctx.execute(str | None) + + def generators( + self, + *, + include: list[str] | None = None, + ) -> GeneratorGroup: + """Return all generators from modules loaded in the workspace. + + Parameters + ---------- + include: + Only include generators matching the specified patterns + """ + _args = [ + Arg("include", include, None), + ] + _ctx = self._select("generators", _args) + return GeneratorGroup(_ctx) + + def git(self) -> "WorkspaceGit": + """Git state for this workspace. Errors if the workspace is not in a git + repository. + """ + _args: list[Arg] = [] + _ctx = self._select("git", _args) + return WorkspaceGit(_ctx) + + async def glob(self, pattern: str) -> list[str]: + """Returns a list of files and directories that match the given pattern. + + Patterns match paths relative to the workspace root. + + 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) -> str: + """A unique identifier for this Workspace. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + def migrate(self) -> "WorkspaceMigration": + """Plan the explicit migration needed for the current workspace. + + The returned plan has an empty changeset and no steps when no + migration is needed. + """ + _args: list[Arg] = [] + _ctx = self._select("migrate", _args) + return WorkspaceMigration(_ctx) + + def module(self, name: str) -> "WorkspaceModule": + """Return a module defined in the workspace configuration. + + Reflects the selected env's effective view. + + Parameters + ---------- + name: + Module name to inspect. + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("module", _args) + return WorkspaceModule(_ctx) + + def module_source(self, path: str) -> ModuleSource: + """Load a module source from a path within the workspace. + + Relative paths (e.g., "foo") resolve from the workspace cwd; absolute + paths (e.g., "/foo") resolve from the workspace root. + + Fails if the path does not point to an initialized module. + + Parameters + ---------- + path: + Location of the module source to load, relative to the workspace + cwd or absolute from the workspace root. + """ + _args = [ + Arg("path", path), + ] + _ctx = self._select("moduleSource", _args) + return ModuleSource(_ctx) + + async def modules(self) -> list["WorkspaceModule"]: + """List modules defined in the workspace configuration. + + Reflects the selected env's effective view. + """ + _args: list[Arg] = [] + _ctx = self._select("modules", _args) + return await _ctx.execute_object_list(WorkspaceModule) + + def reloaded(self) -> Self: + """Return this workspace with its cached host reads invalidated, so + subsequent file and directory reads re-read the live host instead of a + snapshot cached earlier in the session. + """ + _args: list[Arg] = [] + _ctx = self._select("reloaded", _args) + return Workspace(_ctx) + + def sdk(self, name: str) -> "WorkspaceSDK": + """An installed SDK, by name. + + Parameters + ---------- + name: + SDK name to look up. + """ + _args = [ + Arg("name", name), + ] + _ctx = self._select("sdk", _args) + return WorkspaceSDK(_ctx) + + async def sdks(self) -> list["WorkspaceSDK"]: + """Installed SDKs.""" + _args: list[Arg] = [] + _ctx = self._select("sdks", _args) + return await _ctx.execute_object_list(WorkspaceSDK) + + async def search( + self, + pattern: str, + *, + paths: list[str] | None = None, + globs: list[str] | None = None, + literal: bool | None = False, + multiline: bool | None = False, + dotall: bool | None = False, + insensitive: bool | None = False, + skip_ignored: bool | None = False, + skip_hidden: bool | None = False, + files_only: bool | None = False, + limit: int | None = None, + ) -> list[SearchResult]: + """Searches for content matching the given regular expression or literal + string. + + Uses Rust regex syntax; escape literal ., [, ], {, }, | with + backslashes. + + Runs ripgrep on the client host, falling back to grep if unavailable. + + Parameters + ---------- + pattern: + The text to match. + paths: + Directory or file paths to search + globs: + Glob patterns to match (e.g., "*.md") + literal: + Interpret the pattern as a literal string instead of a regular + expression. + multiline: + Enable searching across multiple lines. + dotall: + Allow the . pattern to match newlines in multiline mode. + insensitive: + Enable case-insensitive matching. + skip_ignored: + Honor .gitignore, .ignore, and .rgignore files. + skip_hidden: + Skip hidden files (files starting with .). + files_only: + Only return matching files, not lines and content + limit: + Limit the number of results to return + """ + _args = [ + Arg("pattern", pattern), + Arg("paths", [] if paths is None else paths, []), + Arg("globs", [] if globs is None else globs, []), + Arg("literal", literal, False), + Arg("multiline", multiline, False), + Arg("dotall", dotall, False), + Arg("insensitive", insensitive, False), + Arg("skipIgnored", skip_ignored, False), + Arg("skipHidden", skip_hidden, False), + Arg("filesOnly", files_only, False), + Arg("limit", limit, None), + ] + _ctx = self._select("search", _args) + return await _ctx.execute_object_list(SearchResult) + + def services( + self, + *, + include: list[str] | None = None, + ) -> UpGroup: + """Return all services from modules loaded in the workspace. + + Parameters + ---------- + include: + Only include services matching the specified patterns + """ + _args = [ + Arg("include", include, None), + ] + _ctx = self._select("services", _args) + return UpGroup(_ctx) + + def with_changes(self, changes: Changeset) -> Self: + """Return this workspace with a changeset applied, without mutating the + source. + + Parameters + ---------- + changes: + Changes to apply. + """ + _args = [ + Arg("changes", changes), + ] + _ctx = self._select("withChanges", _args) + return Workspace(_ctx) + + def with_config_env( + self, + name: str, + *, + here: bool | None = False, + ) -> Self: + """Return this workspace with a named config environment created. + + Parameters + ---------- + name: + Environment name. + here: + Write to the workspace config directory at the workspace cwd. + """ + _args = [ + Arg("name", name), + Arg("here", here, False), + ] + _ctx = self._select("withConfigEnv", _args) + return Workspace(_ctx) + + def with_config_value( + self, + key: str, + value: str, + *, + values: list[str] | None = None, + here: bool | None = False, + ) -> Self: + """Return this workspace with a configuration value written. + + When the session selects an env, the key is scoped to that env's + overlay and the env is created if missing. + + Parameters + ---------- + key: + Dotted key path. + value: + Value to set. Bools, integers, and comma-separated arrays are + auto-detected. + values: + List value to set. Elements are stored verbatim, with no auto- + detection. Mutually exclusive with value. + here: + Write to the workspace config directory at the workspace cwd. + """ + _args = [ + Arg("key", key), + Arg("value", value), + Arg("values", values, None), + Arg("here", here, False), + ] + _ctx = self._select("withConfigValue", _args) + return Workspace(_ctx) + + def with_init_client( + self, + path: str, + sdk: str, + module: str, + *, + args: JSON | None = None, + here: bool | None = False, + no_generate: bool | None = False, + ) -> Self: + """Return this workspace with a generated API client initialized. + + The SDK's generators run for the new client, so the returned workspace + carries its generated bindings. + + Parameters + ---------- + path: + Workspace-relative output directory for the generated client. + sdk: + Workspace SDK name or module entry name to use. + module: + Workspace-relative path or canonical ref for the module the client + binds to. + args: + SDK-specific init arguments. + here: + Write to the workspace config directory at the workspace cwd. + no_generate: + Skip running the SDK's generators for the new client. + """ + _args = [ + Arg("path", path), + Arg("sdk", sdk), + Arg("module", module), + Arg("args", args, None), + Arg("here", here, False), + Arg("noGenerate", no_generate, False), + ] + _ctx = self._select("withInitClient", _args) + return Workspace(_ctx) + + def with_init_module( + self, + name: str, + sdk: str, + *, + path: str | None = "", + source: str | None = "", + include: list[str] | None = None, + args: JSON | None = None, + here: bool | None = False, + no_generate: bool | None = False, + ) -> Self: + """Return this workspace with a new module initialized. + + The SDK's generators run for the new module, so the returned workspace + carries the generated code it needs to be loadable. + + Parameters + ---------- + name: + Name of the new module. + sdk: + Workspace SDK name or module entry name to use. + path: + Workspace-relative path for the new module. + source: + Source subpath within the new module. + include: + Additional include patterns for the module. + args: + SDK-specific init arguments. + here: + Write to the workspace config directory at the workspace cwd. + no_generate: + Skip running the SDK's generators for the new module. + """ + _args = [ + Arg("name", name), + Arg("sdk", sdk), + Arg("path", path, ""), + Arg("source", source, ""), + Arg("include", [] if include is None else include, []), + Arg("args", args, None), + Arg("here", here, False), + Arg("noGenerate", no_generate, False), + ] + _ctx = self._select("withInitModule", _args) + return Workspace(_ctx) + + def with_module( + self, + ref: str, + *, + name: str | None = "", + here: bool | None = False, + ) -> Self: + """Return this workspace with a module installed in its config. + + When the session selects an env, the module is recorded in that env's + overlay and the env is created if missing. + + Parameters + ---------- + ref: + Module reference to install. + name: + Override name for the installed module entry. + here: + Write to the workspace config directory at the workspace cwd. + """ + _args = [ + Arg("ref", ref), + Arg("name", name, ""), + Arg("here", here, False), + ] + _ctx = self._select("withModule", _args) + return Workspace(_ctx) + + def with_mounted_directory(self, path: str, source: Directory) -> Self: + """Return this workspace with a directory mounted read-only at the given + path, without mutating the source. + + Mounted content is readable through the normal workspace file tools + but shadows the source at the mount path and stays out of the pending + changeset: it never appears in changes, is never exported, and cannot + be modified. + + Parameters + ---------- + path: + Location of the mounted directory. Relative paths resolve from the + workspace cwd. + source: + Directory to mount. + """ + _args = [ + Arg("path", path), + Arg("source", source), + ] + _ctx = self._select("withMountedDirectory", _args) + return Workspace(_ctx) + + def with_mounted_file(self, path: str, source: File) -> Self: + """Return this workspace with a file mounted read-only at the given path, + without mutating the source. + + Mounted content is readable through the normal workspace file tools + but shadows the source at the mount path and stays out of the pending + changeset: it never appears in changes, is never exported, and cannot + be modified. + + Parameters + ---------- + path: + Location of the mounted file. Relative paths resolve from the + workspace cwd. + source: + File to mount. + """ + _args = [ + Arg("path", path), + Arg("source", source), + ] + _ctx = self._select("withMountedFile", _args) + return Workspace(_ctx) + + def with_new_directory(self, path: str, source: Directory) -> Self: + """Return this workspace with a directory added, without mutating the + source. + + Parameters + ---------- + path: + Path of the added directory. Relative paths resolve from the + workspace cwd. + source: + Directory to add. + """ + _args = [ + Arg("path", path), + Arg("source", source), + ] + _ctx = self._select("withNewDirectory", _args) + return Workspace(_ctx) + + def with_new_file( + self, + path: str, + contents: str, + *, + permissions: int | None = 420, + ) -> Self: + """Return this workspace with a new or replaced file, without mutating + the source. + + Parameters + ---------- + path: + Path of the new file. Relative paths resolve from the workspace + cwd. + contents: + Contents of the new file. + permissions: + Permissions of the new file. + """ + _args = [ + Arg("path", path), + Arg("contents", contents), + Arg("permissions", permissions, 420), + ] + _ctx = self._select("withNewFile", _args) + return Workspace(_ctx) + + def with_sdk( + self, + ref: str, + *, + name: str | None = "", + here: bool | None = False, + as_sdk_name: str | None = "", + ) -> Self: + """Return this workspace with an SDK installed in its config. + + Parameters + ---------- + ref: + SDK module reference to install. + name: + Override name for the installed SDK entry. + here: + Write to the workspace config directory at the workspace cwd. + as_sdk_name: + User-facing SDK name to persist under `[modules..as-sdk] + name = ...`. + """ + _args = [ + Arg("ref", ref), + Arg("name", name, ""), + Arg("here", here, False), + Arg("asSdkName", as_sdk_name, ""), + ] + _ctx = self._select("withSDK", _args) + return Workspace(_ctx) + + def with_updated_lock(self) -> Self: + """Return this workspace with refreshed lockfile state.""" + _args: list[Arg] = [] + _ctx = self._select("withUpdatedLock", _args) + return Workspace(_ctx) + + def with_workdir(self, path: str) -> Self: + """Return this workspace with its working directory pointed at the given + workspace-relative path. + + Parameters + ---------- + path: + Workspace-relative path to use as the working directory. + """ + _args = [ + Arg("path", path), + ] + _ctx = self._select("withWorkdir", _args) + return Workspace(_ctx) + + def without_config_env( + self, + name: str, + *, + here: bool | None = False, + ) -> Self: + """Return this workspace with a named config environment removed. + + Parameters + ---------- + name: + Environment name. + here: + Write to the workspace config directory at the workspace cwd. + """ + _args = [ + Arg("name", name), + Arg("here", here, False), + ] + _ctx = self._select("withoutConfigEnv", _args) + return Workspace(_ctx) + + def without_config_value( + self, + key: str, + *, + here: bool | None = False, + ) -> Self: + """Return this workspace with a configuration value removed. + + Errors when the key is not currently set. + + When the session selects an env, the key is scoped to that env's + overlay. + + Parameters + ---------- + key: + Dotted key path (e.g. modules.greeter.settings.greeting). + here: + Write to the workspace config directory at the workspace cwd. + """ + _args = [ + Arg("key", key), + Arg("here", here, False), + ] + _ctx = self._select("withoutConfigValue", _args) + return Workspace(_ctx) + + def without_directory(self, path: str) -> Self: + """Return this workspace with a directory removed, without mutating the + source. + + Parameters + ---------- + path: + Path of the directory to remove. Relative paths resolve from the + workspace cwd. + """ + _args = [ + Arg("path", path), + ] + _ctx = self._select("withoutDirectory", _args) + return Workspace(_ctx) + + def without_file(self, path: str) -> Self: + """Return this workspace with a file removed, without mutating the + source. + + Parameters + ---------- + path: + Path of the file to remove. Relative paths resolve from the + workspace cwd. + """ + _args = [ + Arg("path", path), + ] + _ctx = self._select("withoutFile", _args) + return Workspace(_ctx) + + def without_module( + self, + name: str, + *, + here: bool | None = False, + ) -> Self: + """Return this workspace with a module removed from its config. + + When the session selects an env, only that env's overlay entry is + removed. + + Parameters + ---------- + name: + Name of the installed module entry to remove. + here: + Write to the workspace config directory at the workspace cwd. + """ + _args = [ + Arg("name", name), + Arg("here", here, False), + ] + _ctx = self._select("withoutModule", _args) + return Workspace(_ctx) + + def without_sdk( + self, + name: str, + *, + here: bool | None = False, + ) -> Self: + """Return this workspace with an SDK removed from its config. + + Parameters + ---------- + name: + Name of the installed SDK entry to remove. + here: + Write to the workspace config directory at the workspace cwd. + """ + _args = [ + Arg("name", name), + Arg("here", here, False), + ] + _ctx = self._select("withoutSDK", _args) + return Workspace(_ctx) + + def with_(self, cb: Callable[["Workspace"], "Workspace"]) -> "Workspace": + """Call the provided callable with current Workspace. + + This is useful for reusability and readability by not breaking the calling chain. + """ + return cb(self) + + +@typecheck +class WorkspaceGit(Type): + """Local git state for a workspace.""" + + def head(self) -> GitRef: + """The checked-out HEAD of this workspace.""" + _args: list[Arg] = [] + _ctx = self._select("head", _args) + return GitRef(_ctx) + + async def id(self) -> str: + """A unique identifier for this WorkspaceGit. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + def uncommitted(self) -> Changeset: + """Uncommitted changes in this workspace, using the same rules as + GitRepository.uncommitted. + """ + _args: list[Arg] = [] + _ctx = self._select("uncommitted", _args) + return Changeset(_ctx) + + +@typecheck +class WorkspaceMigration(Type): + """A planned workspace migration.""" + + def changes(self) -> Changeset: + """Filesystem changes for the full migration plan.""" + _args: list[Arg] = [] + _ctx = self._select("changes", _args) + return Changeset(_ctx) + + async def id(self) -> str: + """A unique identifier for this WorkspaceMigration. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def steps(self) -> list["WorkspaceMigrationStep"]: + """Logical migration steps, each identified by a stable code.""" + _args: list[Arg] = [] + _ctx = self._select("steps", _args) + return await _ctx.execute_object_list(WorkspaceMigrationStep) + + +@typecheck +class WorkspaceMigrationStep(Type): + """A single logical part of a workspace migration.""" + + def changes(self) -> Changeset: + """Filesystem changes for this step.""" + _args: list[Arg] = [] + _ctx = self._select("changes", _args) + return Changeset(_ctx) + + async def code(self) -> str: + """Stable code identifying this logical migration step. + + 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("code", _args) + return await _ctx.execute(str) + + async def description(self) -> str: + """Generic summary of this step's purpose and impact. + + 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) -> str: + """A unique identifier for this WorkspaceMigrationStep. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def warnings(self) -> list[str]: + """Non-fatal warnings raised while planning this step. + + 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("warnings", _args) + return await _ctx.execute(list[str]) + + +@typecheck +class WorkspaceModule(Type): + """A module entry in the workspace configuration.""" + + async def entrypoint(self) -> bool: + """Whether the module is the workspace entrypoint (functions aliased to + Query root). + + 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("entrypoint", _args) + return await _ctx.execute(bool) + + async def id(self) -> str: + """A unique identifier for this WorkspaceModule. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def name(self) -> str: + """The module 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 settings(self) -> list["WorkspaceModuleSetting"]: + """List constructor-backed settings for this module.""" + _args: list[Arg] = [] + _ctx = self._select("settings", _args) + return await _ctx.execute_object_list(WorkspaceModuleSetting) + + async def source(self) -> str: + """The module source path. + + 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 WorkspaceModuleSetting(Type): + """A constructor-backed module setting.""" + + async def description(self) -> str: + """The constructor argument description. + + 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) -> str: + """A unique identifier for this WorkspaceModuleSetting. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def is_list(self) -> bool: + """Whether the setting accepts a list of values. + + 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("isList", _args) + return await _ctx.execute(bool) + + async def key(self) -> str: + """The setting key. + + 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("key", _args) + return await _ctx.execute(str) + + async def value(self) -> str: + """The configured value after applying the selected workspace + environment, or empty when unset. + + 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 WorkspaceSDK(Type): + """An installed SDK: a module marked for scaffolding other modules and + clients.""" + + async def clients(self) -> list[WorkspaceModule]: + """Clients generated with this SDK.""" + _args: list[Arg] = [] + _ctx = self._select("clients", _args) + return await _ctx.execute_object_list(WorkspaceModule) + + async def id(self) -> str: + """A unique identifier for this WorkspaceSDK. + + Note + ---- + This is lazily evaluated, no operation is actually run. + + Returns + ------- + str + The `ID` scalar type represents a unique identifier, often used to + refetch an object or as key for a cache. The ID type appears in a + JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such + as `"4"`) or integer (such as `4`) input value will be accepted as + an ID. + + 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(str) + + async def modules(self) -> list[WorkspaceModule]: + """Modules authored with this SDK.""" + _args: list[Arg] = [] + _ctx = self._select("modules", _args) + return await _ctx.execute_object_list(WorkspaceModule) + + async def name(self) -> str: + """The user-facing SDK 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 ref(self) -> str: + """The module reference this SDK was installed from. + + 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) + + +class Client(Query): + """The Dagger client. + + Inherits all Query API methods and adds connection management. + """ + + +dag = Client() +"""The global client instance.""" + +__all__ = [ + "JSON", + "LLM", + "Address", + "Agent", + "AgentGroup", + "BuildArg", + "CacheSharingMode", + "CacheVolume", + "Changeset", + "ChangesetMergeConflict", + "ChangesetsMergeConflict", + "Check", + "CheckGroup", + "Client", + "ClientFilesyncMirror", + "Cloud", + "Container", + "CurrentModule", + "CurrentModuleAsSDK", + "CurrentModuleAsSDKClient", + "CurrentModuleAsSDKModule", + "DiffStat", + "DiffStatKind", + "Directory", + "Engine", + "EngineCache", + "EngineCacheEntry", + "EngineCacheEntrySet", + "EnumTypeDef", + "EnumValueTypeDef", + "EnvFile", + "EnvVariable", + "Error", + "ErrorValue", + "ExistsType", + "Exportable", + "FieldTypeDef", + "File", + "FileType", + "Function", + "FunctionArg", + "FunctionCachePolicy", + "FunctionCall", + "FunctionCallArgValue", + "GeneratedCode", + "Generator", + "GeneratorGroup", + "GitCommit", + "GitRef", + "GitRepository", + "HTTPState", + "HealthcheckConfig", + "Host", + "ImageLayerCompression", + "ImageMediaTypes", + "InputTypeDef", + "InterfaceTypeDef", + "JSONValue", + "LLMContentBlock", + "LLMContentBlockInput", + "LLMContentBlockKind", + "LLMMessage", + "LLMMessageRole", + "LLMSkill", + "LLMTokenUsage", + "Label", + "ListTypeDef", + "Module", + "ModuleConfigClient", + "ModuleSource", + "ModuleSourceExperimentalFeature", + "ModuleSourceKind", + "NetworkProtocol", + "Node", + "ObjectTypeDef", + "PatchConflict", + "PipelineLabel", + "Platform", + "Port", + "PortForward", + "Query", + "RegistryProtocol", + "RemoteGitMirror", + "ReturnType", + "SDKConfig", + "ScalarTypeDef", + "Schema", + "SearchResult", + "SearchSubmatch", + "Secret", + "Service", + "Socket", + "SourceMap", + "Stat", + "Syncer", + "Terminal", + "TypeDef", + "TypeDefKind", + "Up", + "UpGroup", + "Void", + "Volume", + "Workspace", + "WorkspaceGit", + "WorkspaceMigration", + "WorkspaceMigrationStep", + "WorkspaceModule", + "WorkspaceModuleSetting", + "WorkspaceSDK", + "dag", +] diff --git a/sdk/src/dagger/log.py b/sdk/src/dagger/log.py new file mode 100644 index 0000000..1d0d7ee --- /dev/null +++ b/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/sdk/src/dagger/mod/__init__.py b/sdk/src/dagger/mod/__init__.py new file mode 100644 index 0000000..1ab0293 --- /dev/null +++ b/sdk/src/dagger/mod/__init__.py @@ -0,0 +1,47 @@ +from typing_extensions import Doc + +from dagger.mod._arguments import DefaultAddress +from dagger.mod._arguments import DefaultPath +from dagger.mod._arguments import Deprecated +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() + +agent = _default_mod.agent +check = _default_mod.check +enum_type = _default_mod.enum_type +function = _default_mod.function +field = _default_mod.field +generate = _default_mod.generate +interface = _default_mod.interface +object_type = _default_mod.object_type +up = _default_mod.up + + +def default_module() -> Module: + """Return the default Module builder instance.""" + return _default_mod + + +__all__ = [ + "DefaultAddress", + "DefaultPath", + "Deprecated", + "Doc", # Only re-exported because it's in `typing_extensions`. + "Enum", + "Ignore", + "Name", + "agent", + "check", + "enum_type", + "field", + "function", + "generate", + "interface", + "object_type", + "up", +] diff --git a/sdk/src/dagger/mod/_arguments.py b/sdk/src/dagger/mod/_arguments.py new file mode 100644 index 0000000..ccf8c05 --- /dev/null +++ b/sdk/src/dagger/mod/_arguments.py @@ -0,0 +1,217 @@ +import dataclasses +import inspect +import logging + +from cattrs.preconf.json import JsonConverter + +import dagger +from dagger.mod._exceptions import BadUsageError +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 DefaultAddress: + """If the argument is omitted, load it from the given container address. + + Only applies to arguments of type :py:class:`dagger.Container`. + + 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, ctr: Annotated[dagger.Container, DefaultAddress("alpine:latest")] + ): ... + """ + + address: str + + def __str__(self) -> str: + return self.address + + +@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`/:py:class:`dagger.File` + or :py:class:`dagger.GitRepository`/:py:class:`dagger.GitRef`. + + + 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("..")]): ... + + + @function + def build(self, src: Annotated[dagger.GitRef, DefaultPath("./.git")]): ... + """ + + 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, frozen=True) +class Deprecated: + """Mark a function argument as deprecated. + + Example usage:: + + @function + def old(self, value: Annotated[str, Deprecated("Use new instead")]): ... + """ + + reason: str = "" + + def __str__(self) -> str: + return self.reason + + +@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_address: str | None = None + default_value: dagger.JSON | None = None + deprecated: str | 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 any( + [ + self.has_default, + self.default_path is not None, + self.default_address is not None, + self.is_nullable, + ] + ) + + def _validate(self): + extra = {"parameter": self.signature} + + # 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 BadUsageError(msg, extra=extra) + + if self.default_path: + if self.has_default and not ( + self.is_nullable and self.signature.default is None + ): + msg = ( + f"DefaultPath can't be used in parameter '{self.signature.name}' " + "since it already defines a default value." + ) + raise BadUsageError(msg, extra=extra) + + 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 BadUsageError(msg, extra=extra) + + if self.deprecated and not self.is_optional: + msg = ( + f"Can't deprecate required parameter '{self.signature.name}'. " + "Mark it optional or provide a default value." + ) + raise BadUsageError(msg, extra=extra) diff --git a/sdk/src/dagger/mod/_converter.py b/sdk/src/dagger/mod/_converter.py new file mode 100644 index 0000000..272ce7a --- /dev/null +++ b/sdk/src/dagger/mod/_converter.py @@ -0,0 +1,222 @@ +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() + + 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._ctx.select_id(cls._graphql_name(), 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, context: str = "type") -> "TypeDef": # noqa: C901, PLR0911 + """Convert Python object to API type.""" + if is_initvar(annotation): + return to_typedef(annotation.type, context) + + if is_annotated(annotation): + return to_typedef(strip_annotations(annotation), context) + + td = dag.type_def() + + typ = TypeHint(annotation) + error_msg = f"unsupported {context}: {typ.hint!r}" + + if is_nullable(typ): + td = td.with_optional(True) + + typ = non_null(typ) + + # Can't represent unions in the API. + if is_union(typ): + raise TypeError(error_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) + + raise TypeError(error_msg) diff --git a/sdk/src/dagger/mod/_exceptions.py b/sdk/src/dagger/mod/_exceptions.py new file mode 100644 index 0000000..a55b09b --- /dev/null +++ b/sdk/src/dagger/mod/_exceptions.py @@ -0,0 +1,188 @@ +import json +import logging +import traceback +from collections.abc import Mapping +from typing import Any + +import cattrs +from opentelemetry.semconv.attributes.exception_attributes import ( + EXCEPTION_MESSAGE, + EXCEPTION_STACKTRACE, + EXCEPTION_TYPE, +) + +import dagger +from dagger import DaggerError, dag, telemetry + +logger = logging.getLogger(__package__) + + +class ModuleError(DaggerError): + """Base class for all errors raised by modules. + + This class flags to the entrypoint that the error has been handled so we + can exit cleanly, without dumping the full traceback even when it's not + useful. + + It also allows control over what gets reported to dag.error() via the + error message and extra values. + """ + + def __init__(self, /, *args, extra: Mapping[str, Any] | None = None): + super().__init__(*args) + self.extra = extra + + +class ModuleLoadError(ModuleError): + """Error while loading Python module with functions.""" + + +class InvalidInputError(ModuleError): + """Error while deserializing values into Python objects. + + If it happens it's probably a bug in the SDK because the API should + validate early if input is not of expected type. + """ + + +class InvalidResultError(ModuleError): + """Error while serializing Python values into JSON.""" + + +class ObjectNotFoundError(ModuleError): + """Parent object not found on registry.""" + + +class RegistrationError(ModuleError): + """An error caused by an invalid type def registration.""" + + +class BadUsageError(ModuleError): + """A usage error.""" + + +class FunctionError(ModuleError): + """An error while executing a user function.""" + + +def transform_error( + exc: Exception, + msg: str = "", + origin: Any | None = None, + typ: type | None = None, +) -> str: + """Transform an exception raised by cattrs into an error message.""" + kwargs = {} + if origin is not None: + path = getattr(origin, "__qualname__", "") + if hasattr(origin, "__module__"): + path = f"{origin.__module__}.{path}" + + if path: + kwargs["path"] = path + + if msg: + msg += ": " + + # cattrs.transform_error sets expected type as None when not a cattrs exception. + if typ is not None and not isinstance(exc, cattrs.BaseValidationError): + msg += cattrs.v.format_exception(exc, typ) + if path := kwargs.get("path"): + msg = f"{msg} @ {path}" + else: + msg += "; ".join( + error.removesuffix(" $").removesuffix(" @") + for error in cattrs.transform_error(exc, **kwargs) + ) + + return msg + + +def log_exception_only( + exc: Exception, + msg: str, + *args, + # Extra note to add to the end of the error message when shown on + # the log, but not included in the dagger.Error message. + note: str = "", +): + """Log just this exception, without full traceback. + + Generates an error log message just for this exception, not the full + traceback, including without any chained exceptions. + + This should be used in cases where this exception has enough information + or when the rest of the traceback doesn't add anything particularly useful, + so there's less noise to sort through while debugging. + + The full traceback will still be included in dag.Error() values which + could at some point be optionally shown in the web/cloud UI at some + point, but it's also fully available to LLM in the meantim. + """ + if note and hasattr(exc, "add_note"): + exc.add_note(note) + logger.error(msg, *args, exc_info=(type(exc), exc, None)) + + +async def record_exception(exc: Exception): + """Convert a Python exception into a `dagger.Error`.""" + attrs: dict[str, Any] = _exception_attributes(exc) + msg = f"{attrs[EXCEPTION_TYPE]}: {attrs[EXCEPTION_MESSAGE]}" + + if isinstance(exc, ModuleError) and exc.extra: + extra = {f"extra.{key}": val for key, val in exc.extra.items()} + # ModuleError extra values don't conflict with the OTel attributes + # but prepending like this avoids a future mistake. + attrs = {**extra, **attrs} + + # Preserve original API error so it's properly propagated. + if isinstance(exc, dagger.QueryError): + msg = str(exc) + attrs.update(exc.error.extensions) + + dag_err = dag.error(msg) + for key, value in attrs.items(): + dag_err = dag_err.with_value(key, dagger.JSON(_safe_json_dumps(value))) + + await dag.current_function_call().return_error(dag_err) + + # When an error occurs within a started span context the OTel SDK + # automatically sends an event with details about the exception. + # Switching to dag.Error doesn't take advantage of that and the engine + # doesn't recreate the exception event on the parent function span. + # Still, recording the exception manually can be useful when analyzing the + # raw telemetry in e.g., Honeycomb. + with telemetry.get_tracer().start_as_current_span( + "recording Python exception", + # TODO: even with following attribute it's still being shown in the + # Cloud UI. + attributes={"dagger.io/ui.internal": True}, + ) as span: + span.record_exception(exc) + + +def _exception_attributes(exc: Exception) -> dict[str, str]: + message = str(exc) + stacktrace = "".join(traceback.format_exception(exc)) + module = type(exc).__module__ + qualname = type(exc).__qualname__ + exc_type = f"{module}.{qualname}" if module and module != "builtins" else qualname + + # hide the full `dagger.mod._exception` module path + exc_type = exc_type.replace(__name__ + ".", "") + + # Reusing OTel attribute names for consistency. + return { + EXCEPTION_TYPE: exc_type, + EXCEPTION_MESSAGE: message, + EXCEPTION_STACKTRACE: stacktrace, + } + + +def _safe_json_dumps(value: Any) -> str: + """Safely serialize value to JSON, falling back to repr() if not serializable.""" + try: + return json.dumps(value) + except (TypeError, ValueError): + # Fall back to string representation for non-serializable values + return json.dumps(repr(value)) diff --git a/sdk/src/dagger/mod/_module.py b/sdk/src/dagger/mod/_module.py new file mode 100644 index 0000000..16e730e --- /dev/null +++ b/sdk/src/dagger/mod/_module.py @@ -0,0 +1,1042 @@ +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._converter import make_converter, to_typedef +from dagger.mod._exceptions import ( + BadUsageError, + FunctionError, + InvalidInputError, + InvalidResultError, + ObjectNotFoundError, + RegistrationError, + log_exception_only, + transform_error, +) +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, + extract_enum_member_doc, + get_doc, + get_parent_module_doc, + is_annotated, +) + +logger = logging.getLogger(__package__) + +OBJECT_DEF_KEY: typing.Final[str] = "__dagger_object__" +FIELD_DEF_KEY: typing.Final[str] = "__dagger_field__" +FUNCTION_DEF_KEY: typing.Final[str] = "__dagger_function__" +CHECK_DEF_KEY: typing.Final[str] = "__dagger_check__" +GENERATOR_DEF_KEY: typing.Final[str] = "__dagger_generate__" +UP_DEF_KEY: typing.Final[str] = "__dagger_up__" +AGENT_DEF_KEY: typing.Final[str] = "__dagger_agent__" +MODULE_NAME: typing.Final[str] = os.getenv("DAGGER_MODULE", "") +MAIN_OBJECT: typing.Final[str] = os.getenv("DAGGER_MAIN_OBJECT", "") +TYPE_DEF_FILE: typing.Final[str] = os.getenv("DAGGER_MODULE_FILE", "/module.json") + +T = TypeVar("T", bound=type) + + +class Module: + """Builder for a :py:class:`dagger.Module`.""" + + def __init__(self, main_name: str = MAIN_OBJECT): + self._main_name = main_name + self._converter: JsonConverter = make_converter() + self._objects: dict[str, ObjectType] = {} + self._enums: dict[str, type[enum.Enum]] = {} + self._main: ObjectType | None = None + # Escape hatch if there's too much noise from showing stack traces + # from exceptions raised in functions by default. Not documented + # intentionally for now. + self.log_exceptions = True + + @property + def main_cls(self) -> type[ObjectType]: + 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 + + async def serve(self): + if await dag.current_function_call().parent_name(): + result = await self.invoke() + else: + try: + result = await self._typedefs() + except TypeError as e: + raise RegistrationError(str(e)) from e + + try: + output = json.dumps(result) + except (TypeError, ValueError) as e: + # Not expected to happen because unstructuring should reduce + # Python complex types to primitive values that are easily + # serialized to JSON. If not, it's something that should be caught + # earlier. + msg = f"Failed to serialize final result as JSON: {e}" + raise InvalidResultError(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): + """Register the module and its types with the Dagger API.""" + try: + result = await self._typedefs() + output = json.dumps(result) + except TypeError as e: + raise RegistrationError(str(e), e) from e + await anyio.Path(TYPE_DEF_FILE).write_text(output) + + async def _typedefs(self) -> str: # noqa: C901, PLR0912, PLR0915 + if not self._main_name: + msg = "Main object name can't be empty" + raise ValueError(msg) + try: + self.get_object(self._main_name) + except ObjectNotFoundError as e: + msg = ( + f"Main object with name '{self._main_name}' not found or class not " + "decorated with '@dagger.object_type'\n" + f"If you believe the module name '{MODULE_NAME}' is incorrectly " + "being converted into PascalCase, please file a bug report." + ) + raise ObjectNotFoundError(msg, extra=e.extra) from None + + 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), + deprecated=obj_type.deprecated, + ) + + # Object fields + if obj_type.fields: + types = typing.get_type_hints(obj_type.cls) + + for field_name, field in obj_type.fields.items(): + ctx = f"type for field '{field.original_name}' in {obj_type}" + type_def = type_def.with_field( + field_name, + to_typedef(types[field.original_name], ctx), + description=get_doc(field.return_type), + deprecated=field.meta.deprecated, + ) + + # Object/interface functions + for func_name, func in obj_type.functions.items(): + what = f"function '{func_name}'" if func_name else "constructor" + + func_def = dag.function( + func_name, + to_typedef( + func.return_type, + f"return type for {what} in {obj_type}", + ), + ) + + if doc := func.doc: + func_def = func_def.with_description(doc) + + if func.cache_policy is not None: + if func.cache_policy == "never": + func_def = func_def.with_cache_policy( + dagger.FunctionCachePolicy.Never, + ) + elif func.cache_policy == "session": + func_def = func_def.with_cache_policy( + dagger.FunctionCachePolicy.PerSession, + ) + elif func.cache_policy != "": + func_def = func_def.with_cache_policy( + dagger.FunctionCachePolicy.Default, + time_to_live=func.cache_policy, + ) + if deprecated := func.deprecated: + func_def = func_def.with_deprecated(reason=deprecated) + if func.check: + func_def = func_def.with_check() + if func.generate: + func_def = func_def.with_generator() + if func.service: + func_def = func_def.with_up() + if func.agent: + func_def = func_def.with_agent() + + for param in func.parameters.values(): + arg_def = to_typedef( + param.resolved_type, + f"parameter type for '{param.name}' in {what} and {obj_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, + default_address=param.default_address, + ignore=param.ignore, + deprecated=param.deprecated, + ) + + type_def = ( + type_def.with_constructor(func_def) + if func_name == "" + else type_def.with_function(func_def) + ) + + # Add object/interface to module + mod = ( + mod.with_interface(type_def) + if obj_type.interface + else 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: + description = getattr(member, "description", None) + meta = member_docs.get(member.name) + + if description is None and meta and meta.description is not None: + description = meta.description + + enum_def = enum_def.with_enum_member( + member.name, + value=str(member.value), + description=description, + deprecated=meta.deprecated if meta else None, + ) + mod = mod.with_enum(enum_def) + + return await mod.id() + + async def invoke(self) -> str: + """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() + parent_name = await fn_call.parent_name() + + if not parent_name: + msg = ( + "Seems like the SDK module isn't registering the types correctly. " + "This is a bug." + ) + raise RegistrationError(msg) + + 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: + logger.exception("Failed to decode JSON parent value") + msg = "Unable to decode the parent object's state" + extra = { + "parent_json": parent_json, + } + raise InvalidInputError(msg, extra=extra) 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: + logger.exception("Failed to decode JSON input value") + msg = f"Unable to decode input argument '{arg_name}'" + extra = { + "json_value": arg_value, + } + raise InvalidInputError(msg, extra=extra) 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_result( + self, + parent_name: str, + parent_state: Mapping[str, Any], + name: str, + raw_inputs: Mapping[str, Any], + ) -> Any: + """Get function result as an unstructured Python primitive.""" + result, fn = await self.get_structured_result( + parent_name, + parent_state, + name, + raw_inputs, + ) + if fn.return_type is not None: + try: + return await self.unstructure(result, fn.return_type) + except Exception as e: + log_exception_only(e, "Invalid result from function") + msg = transform_error( + e, + origin=getattr(fn, "wrapped", None), + typ=fn.return_type, + ) + msg += ( + "\n" + "Please check if the returned value at runtime matches " + "the function's declared return type." + ) + raise InvalidResultError(msg) from e + return None + + async def get_structured_result( + self, + parent_name: str, + parent_state: Mapping[str, Any], + name: str, + raw_inputs: Mapping[str, Any], + ) -> tuple[Any, Field | Function]: + """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, 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 + + fn = obj_type.get_bound_function(parent, name) + + inputs = await self._convert_inputs(fn, 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)) + + result = await self.call(fn.wrapped, *bound.args, **bound.kwargs) + + # Provide better errors for missing async/await + if inspect.iscoroutine(result): + result.close() # avoid RuntimeWarning + + if not inspect.iscoroutinefunction(fn.wrapped): + msg = ( + f"Function '{fn}' returned a coroutine.\n" + "Did you forget to add 'async' to the function signature?" + ) + else: + msg = ( + f"Async function '{fn}' was never awaited.\n" + "Did you forget to add an 'await' to the return value?" + ) + raise FunctionError(msg) from None + + return result, fn + + async def call(self, func: Func[P, R], *args: P.args, **kwargs: P.kwargs) -> R: + """Call a function and return its result.""" + try: + # We could await based on the return value instead of checking function + # color but that would silently allow incorrect code which is + # especially bad if not intentional and we don't warn user about it. + result = func(*args, **kwargs) + if inspect.iscoroutinefunction(func): + result = await cast(typing.Awaitable[R], result) + except FunctionError: + # Escape hatch to fully control logging from user code. + raise + except dagger.QueryError as e: + tb = e.__traceback__ + # Exclude the line in "try" above + if tb: + tb = tb.tb_next + # Exclude the underlying TransportQueryError to reduce noise + e.__cause__ = None + logger.exception( + "API error while executing function", + exc_info=(type(e), e, tb), + ) + # Preserve API error so it's properly propagated. + raise e from None + except Exception as e: + # Escape hatch if too noisy. + if self.log_exceptions: + # Logging the exception will show the full stack trace on stderr. + logger.exception("Unhandled exception while executing function") + raise FunctionError(str(e)) from e + + return result + + async def structure(self, obj: Any, cl: type[T]) -> T: + """Convert a primitive value to the expected type.""" + return await asyncify(self._converter.structure, obj, cl) + + async def unstructure(self, obj: Any, unstructure_as: Any) -> Awaitable[Any]: + """Convert a result to primitive values.""" + return await asyncify(self._converter.unstructure, obj, unstructure_as) + + def get_object(self, name: str) -> ObjectType: + """Get the object type definition for the given name.""" + try: + return self._objects[name] + except KeyError: + # Not expected to happen during invoke because registration should + # fail first. + msg = f"No '@dagger.object_type' decorated class named '{name}' was found" + extra = {"objects_found": self._objects.keys()} + raise ObjectNotFoundError(msg, extra=extra) from None + + async def _get_parent_instance( + self, + obj_type: ObjectType[T], + state: Mapping[str, Any], + ) -> T: + """Instantiate the parent object from its state.""" + try: + return await self.structure(state, obj_type.cls) + except Exception as e: + log_exception_only(e, "Failed to instantiate parent object") + msg = transform_error( + e, + f"Failed to instantiate parent object '{obj_type}'", + origin=obj_type.cls, + typ=obj_type.cls, + ) + # If API is able to make the call this is likely a bug in the SDK. + # For example, if the registration phase reports a type that isn't + # compatible with cattrs' converter. + msg += ( + "\n" + "This could be an error in the Python SDK. " + "If so, please file a bug report." + ) + extra = {"object_state": state} + raise InvalidInputError(msg, extra=extra) from e + + async def _convert_inputs( + self, + fn: Function, + 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 fn.parameters.items(): + if param.name not in inputs: + if not param.is_optional: + msg = f"Missing required function argument '{python_name}'" + raise InvalidInputError(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 Exception as e: + log_exception_only( + e, + "Failed to convert from primitive input value for argument '%s'", + param.name, + ) + msg = transform_error( + e, + ( + "Failed to convert from primitive input value for argument " + f"'{param.name}'" + ), + origin=fn.wrapped, + typ=type_, + ) + # Same as before, the API can't reasonably hold a value that + # contradicts its type. + msg += ( + "\n" + "This could be an error in the Python SDK. " + "If so, please file a bug report." + ) + extra = { + "function_name": fn.original_name, + "parameter_name": python_name, + "expected_type": type_, + "actual_type": type(value), + } + raise InvalidInputError(msg, extra=extra) 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, + deprecated: str | None = None, + ) -> 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`. + deprecated: + Optional deprecation message exposed to the engine. + """ + 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, deprecated)}, + kw_only=True, + init=init, + repr=init, # default repr shows field as an __init__ argument + **kwargs, + ) + + def check( + self, + func: Func[P, R] | None = None, + ) -> Func[P, R] | Callable[[Func[P, R]], Func[P, R]]: + """Mark a function as a check. + + Checks are functions that validate conditions and return void/error + to indicate pass/fail. This decorator can be combined with + :py:meth:`function`. + + Example usage:: + + @object_type + class MyModule: + @function + @check + def lint(self) -> str: + return "All checks passed" + + Parameters + ---------- + func: + The function to mark as a check. Should be an instance method in a + class decorated with :py:meth:`object_type`. + """ + + def wrapper(fn: Func[P, R]) -> Func[P, R]: + setattr(fn, CHECK_DEF_KEY, True) + return fn + + return wrapper(func) if func else wrapper + + def generate( + self, + func: Func[P, R] | None = None, + ) -> Func[P, R] | Callable[[Func[P, R]], Func[P, R]]: + """Mark a function as a generator. + + Generators are functions that return a Changeset representing + changes to be applied. This decorator can be combined with + :py:meth:`function`. + + Example usage:: + + @object_type + class MyModule: + @function + @generate + def codegen(self) -> dagger.Changeset: + # Generate code and return changeset + ... + + Parameters + ---------- + func: + The function to mark as a generator. Should be an instance method in a + class decorated with :py:meth:`object_type`. + """ + + def wrapper(fn: Func[P, R]) -> Func[P, R]: + setattr(fn, GENERATOR_DEF_KEY, True) + return fn + + return wrapper(func) if func else wrapper + + def up( + self, + func: Func[P, R] | None = None, + ) -> Func[P, R] | Callable[[Func[P, R]], Func[P, R]]: + """Mark a function as a service for ``dagger up``.""" + + def wrapper(fn: Func[P, R]) -> Func[P, R]: + setattr(fn, UP_DEF_KEY, True) + return fn + + return wrapper(func) if func else wrapper + + def agent( + self, + func: Func[P, R] | None = None, + ) -> Func[P, R] | Callable[[Func[P, R]], Func[P, R]]: + """Mark a function as an agent middleware. + + Agent middlewares take a base :py:class:`dagger.LLM` and return an + ``LLM`` with the module's tools and prompting folded onto it. They are + discovered and composed by ``dagger agent``. Besides the base ``LLM``, + an agent function may not declare any other required argument. This + decorator can be combined with :py:meth:`function`. + + Example usage:: + + @object_type + class MyModule: + @function + @agent + def agent(self, base: dagger.LLM) -> dagger.LLM: + return base.with_tools(dag.current_node()) + + Parameters + ---------- + func: + The function to mark as an agent middleware. Should be an instance + method in a class decorated with :py:meth:`object_type`. + """ + + def wrapper(fn: Func[P, R]) -> Func[P, R]: + setattr(fn, AGENT_DEF_KEY, True) + return fn + + return wrapper(func) if func else wrapper + + @overload + def function( + self, + func: Func[P, R], + *, + name: APIName | None = None, + doc: str | None = None, + deprecated: str | None = None, + ) -> Func[P, R]: ... + + @overload + def function( + self, + *, + name: APIName | None = None, + doc: str | None = None, + deprecated: 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, + cache: str | None = None, + deprecated: 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. + deprecated: + Optional deprecation message exposed to the engine. + """ + + # 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)}." + + # Check if function is marked as a check or generator + check = getattr(func, CHECK_DEF_KEY, False) + generator = getattr(func, GENERATOR_DEF_KEY, False) + service = getattr(func, UP_DEF_KEY, False) + agent = getattr(func, AGENT_DEF_KEY, False) + + meta = FunctionDefinition( + name=name, + doc=doc, + cache=cache, + deprecated=deprecated, + check=check, + generator=generator, + service=service, + agent=agent, + ) + + 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, /, *, deprecated: str | None = None) -> T: ... + + @overload + @dataclass_transform( + kw_only_default=True, + field_specifiers=(function, dataclasses.field, dataclasses.Field), + ) + def object_type(self, *, deprecated: str | None = None) -> Callable[[T], T]: ... + + def object_type( + self, + cls: T | None = None, + *, + deprecated: str | 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" + + + Parameters + ---------- + deprecated: + Optional deprecation message visible when introspecting the module. + """ + + def wrapper(cls: T) -> T: + if not inspect.isclass(cls): + msg = f"Expected a class, got {type(cls)}" + raise BadUsageError(msg) + + # Check for InitVar inside Annotated + 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 BadUsageError(msg) + + wrapped = dataclasses.dataclass(kw_only=True)(cls) + return self._process_type(wrapped, deprecated=deprecated) + + return wrapper(cls) if cls else wrapper + + def _process_type( + self, + cls: T, + *, + interface: bool = False, + deprecated: str | None = None, + ) -> T: + obj_def = ObjectType(cls, interface=interface, deprecated=deprecated) + + cls.__dagger_module__ = self + cls.__dagger_object_type__ = obj_def + self._objects[cls.__name__] = obj_def + if cls.__name__ == self._main_name: + self._main = 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.Enum subclass, got {type(cls)}" + raise BadUsageError(msg) + + if not issubclass(cls, enum.Enum): + msg = f"Class '{cls.__name__}' is not an enum.Enum subclass" + raise BadUsageError(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/sdk/src/dagger/mod/_resolver.py b/sdk/src/dagger/mod/_resolver.py new file mode 100644 index 0000000..993faff --- /dev/null +++ b/sdk/src/dagger/mod/_resolver.py @@ -0,0 +1,316 @@ +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 ( + BadUsageError, + InvalidInputError, + RegistrationError, +) +from dagger.mod._types import APIName, FieldDefinition, FunctionDefinition, PythonName +from dagger.mod._utils import ( + get_alt_constructor, + get_alt_name, + get_default_address, + get_default_path, + get_deprecated, + get_doc, + get_ignore, + is_nullable, + is_self, + list_of, + normalize_name, +) + +CHECK_DEF_KEY: str = "__dagger_check__" +GENERATOR_DEF_KEY: str = "__dagger_generate__" +UP_DEF_KEY: str = "__dagger_up__" +AGENT_DEF_KEY: str = "__dagger_agent__" + +logger = logging.getLogger(__package__) + +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): + if self.origin is not None: + return f"{self.origin.__name__}.{self.original_name}" + return self.original_name + + def __repr__(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) + + @property + def deprecated(self) -> str | None: + """Return the deprecation message for the callable, if any.""" + return self.meta.deprecated + + @property + def check(self) -> bool: + """Indicates whether the function is configured as a check.""" + # Check both the metadata and the attribute to support either decorator order + return self.meta.check or getattr(self.wrapped, CHECK_DEF_KEY, False) + + @property + def generate(self) -> bool: + """Indicates whether the function is configured as a generator.""" + # Check both the metadata and the attribute to support either decorator order + return self.meta.generator or getattr(self.wrapped, GENERATOR_DEF_KEY, False) + + @property + def service(self) -> bool: + """Indicates whether the function is configured as a service for dagger up.""" + # Check both the metadata and the attribute to support either decorator order + return self.meta.service or getattr(self.wrapped, UP_DEF_KEY, False) + + @property + def agent(self) -> bool: + """Indicates whether the function is configured as an agent middleware.""" + # Check both the metadata and the attribute to support either decorator order + return self.meta.agent or getattr(self.wrapped, AGENT_DEF_KEY, False) + + @cached_property + def cache_policy(self): + return self.meta.cache + + @cached_property + def type_hints(self): + return get_type_hints(self.wrapped) + + @cached_property + def type_hints_with_extras(self): + """Type hints with Annotated metadata preserved. + + Used for extracting metadata like DefaultPath, Doc, Name, etc. + from parameters when `from __future__ import annotations` is used. + """ + return get_type_hints(self.wrapped, include_extras=True) + + @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 BadUsageError(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 + + # Get the annotated type (with Annotated preserved) for metadata extraction. + # This is needed when `from __future__ import annotations` is used, + # which causes param.annotation to be a string instead of a type. + try: + annotated_type = self.type_hints_with_extras[param.name] + except KeyError: + annotated_type = param.annotation + + return Parameter( + name=get_alt_name(annotated_type) or normalize_name(param.name), + signature=param, + resolved_type=annotation, + is_nullable=is_nullable(TypeHint(annotation)), + doc=get_doc(annotated_type), + ignore=get_ignore(annotated_type), + default_path=get_default_path(annotated_type), + default_address=get_default_address(annotated_type), + deprecated=get_deprecated(annotated_type), + 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: + logger.exception("Unexpected type while binding input values to arguments") + raise InvalidInputError(str(e)) 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 + deprecated: str | None = None + 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: + msg = f"No function '{name}' in {self}" + raise RegistrationError(msg) from None + + return fn.bind_parent(parent) + + def __str__(self): + s = "interface" if self.interface else "object" + return f"{s} '{self.cls.__module__}.{self.cls.__name__}'" diff --git a/sdk/src/dagger/mod/_types.py b/sdk/src/dagger/mod/_types.py new file mode 100644 index 0000000..8e6e771 --- /dev/null +++ b/sdk/src/dagger/mod/_types.py @@ -0,0 +1,58 @@ +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 + deprecated: str | None = None + + +@dataclasses.dataclass(slots=True, frozen=True) +class FunctionDefinition: + name: APIName | None = None + doc: str | None = None + cache: str | None = None + deprecated: str | None = None + check: bool = False + generator: bool = False + service: bool = False + agent: bool = False + + +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/sdk/src/dagger/mod/_utils.py b/sdk/src/dagger/mod/_utils.py new file mode 100644 index 0000000..6149876 --- /dev/null +++ b/sdk/src/dagger/mod/_utils.py @@ -0,0 +1,368 @@ +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 DefaultAddress, DefaultPath, Deprecated, 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 + + +@dataclasses.dataclass(slots=True) +class EnumMemberDoc: + description: str | None = None + deprecated: str | None = None + + +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_default_address(obj: Any) -> str | None: + """Get the last DefaultAddress() of an annotated type.""" + meta = get_meta(obj, DefaultAddress) + return meta.address 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 get_deprecated(obj: Any) -> str | None: + """Get the deprecation metadata from an annotated type.""" + if meta := get_meta(obj, Deprecated): + return meta.reason + return 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 _parse_enum_docstring(text: str) -> EnumMemberDoc: + description_lines: list[str] = [] + deprecated_lines: list[str] = [] + lines = text.splitlines() + it = iter(enumerate(lines)) + for _, raw_line in it: + stripped = raw_line.strip() + if stripped.startswith(".. deprecated::"): + # capture first line after the directive + remainder = stripped[len(".. deprecated::") :].strip() + if remainder: + deprecated_lines.append(remainder) + # grab any indented continuation lines + for _, cont in it: + cont_stripped = cont.strip() + if not cont_stripped: + continue + if cont.startswith((" ", "\t")): + deprecated_lines.append(cont_stripped) + continue + # hit a non-indented line: feed it back into the outer loop + description_lines.append(cont_stripped) + break + else: + description_lines.append(stripped) + description = "\n".join(line for line in description_lines if line).strip() + deprecated = "\n".join(line for line in deprecated_lines if line).strip() + return EnumMemberDoc( + description=description or None, + deprecated=deprecated or None, + ) + + +def extract_enum_member_doc(cls: type[enum.Enum]) -> dict[str, EnumMemberDoc]: + """Extract docstrings for enum members by parsing the AST.""" + member_docs: dict[str, EnumMemberDoc] = {} + + 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] = _parse_enum_docstring(doc) + + return member_docs diff --git a/sdk/src/dagger/mod/cli.py b/sdk/src/dagger/mod/cli.py new file mode 100644 index 0000000..86d0866 --- /dev/null +++ b/sdk/src/dagger/mod/cli.py @@ -0,0 +1,104 @@ +"""Command line interface for the dagger extension runtime.""" + +import importlib +import importlib.metadata +import importlib.util +import logging +import os +import typing + +import anyio + +import dagger +from dagger import telemetry +from dagger.mod._exceptions import ModuleError, ModuleLoadError, record_exception +from dagger.mod._module import MAIN_OBJECT, Module + +logger = logging.getLogger(__package__) + +ENTRY_POINT_NAME: typing.Final[str] = "main_object" +ENTRY_POINT_GROUP: typing.Final[str] = typing.cast(str, __package__) +IMPORT_PKG: typing.Final[str] = os.getenv("DAGGER_DEFAULT_PYTHON_PACKAGE", "main") + + +def app(mod: Module | None = None, register: bool = False) -> int | None: + """Entrypoint for a Python Dagger module.""" + telemetry.initialize() + try: + return anyio.run(main, mod, register) + finally: + telemetry.shutdown() + + +async def main(mod: Module | None = None, register: bool = False) -> int | None: + """Async entrypoint for a Dagger module.""" + # Establishing connection early on to allow returning dag.error(). + # Note: if there's a connection error dag.error() won't be sent but + # should be logged and the traceback shown on the function's stderr output. + async with await dagger.connect(): + try: + if mod is None: + mod = load_module() + if register: + return await mod.register() + return await mod.serve() + except (ModuleError, dagger.QueryError) as e: + await record_exception(e) + return 2 + except Exception as e: + logger.exception("Unhandled exception") + await record_exception(e) + return 1 + + +def load_module() -> Module: + """Load the dagger.Module instance via the main object entry point.""" + ep = get_entry_point() + try: + cls = ep.load() + except Exception as e: + logger.exception( + "Error while importing Python module '%s' with Dagger functions", + ep.module, + ) + raise ModuleLoadError(str(e)) from e + try: + return cls.__dagger_module__ + except AttributeError: + msg = ( + "The main object must be a class decorated with @dagger.object_type, " + f"found '{type(cls)}'" + ) + raise ModuleLoadError(msg) from None + + +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" + + if not importlib.util.find_spec(import_pkg): + 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 ModuleLoadError(msg) + + return importlib.metadata.EntryPoint( + group=ENTRY_POINT_GROUP, + name=ENTRY_POINT_NAME, + value=f"{import_pkg}:{MAIN_OBJECT}", + ) diff --git a/sdk/src/dagger/provisioning/__init__.py b/sdk/src/dagger/provisioning/__init__.py new file mode 100644 index 0000000..39599aa --- /dev/null +++ b/sdk/src/dagger/provisioning/__init__.py @@ -0,0 +1,13 @@ +# Make sure Config is imported before Connection +from ._config import Config +from ._connection import Connection, connection +from ._exceptions import DownloadError, ProvisionError, SessionError + +__all__ = [ + "Config", + "Connection", + "DownloadError", + "ProvisionError", + "SessionError", + "connection", +] diff --git a/sdk/src/dagger/provisioning/_config.py b/sdk/src/dagger/provisioning/_config.py new file mode 100644 index 0000000..4be749d --- /dev/null +++ b/sdk/src/dagger/provisioning/_config.py @@ -0,0 +1,83 @@ +import dataclasses +from os import PathLike +from typing import Any, TextIO + +from rich.console import Console + +from dagger.client._config import ConnectConfig, Timeout + +UNSET = object() + + +@dataclasses.dataclass(slots=True, kw_only=True) +class Config(ConnectConfig): + """Options for connecting to the Dagger engine. + + Parameters + ---------- + timeout: + The maximum time in seconds for establishing a connection to the server, + or None to disable. Defaults to 10 seconds. + retry: + Retry parameters for connecting to the Dagger API server. + workdir: + The host workdir loaded into dagger. + config_path: + Project config file. + log_output: + A TextIO object to send the logs from the engine. + load_workspace_modules: + Opt into loading workspace modules for this connection. By default, + only the core API is exposed. + execute_timeout: + The maximum time in seconds for the execution of a request before an + ExecuteTimeoutError is raised. Passing None results in waiting forever for a + response (default). + """ + + workdir: PathLike[str] | str = "" + config_path: PathLike[str] | str = "" + log_output: TextIO | None = None + load_workspace_modules: bool = False + execute_timeout: Any = UNSET + console: Console = dataclasses.field(init=False) + + def __post_init__(self): + # Backwards compatibility for (expected) use of `timeout` config. + if self.timeout and not isinstance(self.timeout, Timeout): + # TODO: deprecation warning: Use + # self.timeout hasn't worked! (unused) + timeout = self.timeout + + # used to be int + try: + timeout = float(timeout) + except TypeError as e: + msg = f"Wrong type for timeout: {type(timeout)}" + raise TypeError(msg) from e + + self.timeout = Timeout(None, connect=timeout) + + # Backwards compatibility for `execute_timeout` config. + if self.execute_timeout is not UNSET: + # TODO: deprecation warning: Use `timeout` instead. + timeout = self.execute_timeout + + # used to be int | float | None + if timeout is not None: + try: + timeout = float(timeout) + except TypeError as e: + msg = f"Wrong type for execute_timeout: {type(timeout)}" + raise TypeError(msg) from e + + self.timeout = ( + Timeout(timeout, connect=self.timeout.connect) + if self.timeout + else Timeout(timeout) + ) + + self.console = Console( + file=self.log_output, + stderr=True, + ) diff --git a/sdk/src/dagger/provisioning/_connection.py b/sdk/src/dagger/provisioning/_connection.py new file mode 100644 index 0000000..630ed74 --- /dev/null +++ b/sdk/src/dagger/provisioning/_connection.py @@ -0,0 +1,120 @@ +import contextlib +import logging +from typing import TYPE_CHECKING + +from dagger import telemetry +from dagger._managers import ResourceManager + +from ._config import Config +from ._engine import Engine, provision_engine + +if TYPE_CHECKING: + from dagger import Client + +logger = logging.getLogger(__name__) + + +class Connection(ResourceManager): + """Connect to a Dagger Engine with an isolated client (legacy). + + This is an older version of :py:func:`dagger.connection` that uses an isolated + client instance. Should no longer be used in newer projects unless there's + a specific reason to do so. + + Example:: + + import dagger + + + async def main(): + async with dagger.Connection() as client: + ctr = client.container().from_("alpine") + + + You can stream the logs from the engine to see progress:: + + import sys + import anyio + import dagger + + + async def main(): + cfg = dagger.Config(log_output=sys.stderr) + + async with dagger.Connection(cfg) as client: + ctr = client.container().from_("python:3.11.1-alpine") + version = await ctr.with_exec(["python", "-V"]).stdout() + + print(version) + # Output: Python 3.11.1 + + + anyio.run(main) + """ + + def __init__(self, config: Config | None = None) -> None: + super().__init__() + self.cfg = config or Config() + + async def __aenter__(self) -> "Client": + telemetry.initialize() + logger.debug("Establishing connection with isolated client") + async with self.get_stack() as stack: + engine = await Engine(self.cfg, stack).provision() + conn = engine.get_client_connection() + return await engine.setup_client(conn) + + async def close(self): + logger.debug("Closing connection with isolated client") + await super().close() + + +@contextlib.asynccontextmanager +async def connection(config: Config | None = None): + """Connect to a Dagger Engine using the global client. + + This is similar to :py:class:`dagger.Connection` but uses a global client + (:py:attr:`dagger.dag`) so there's no need to pass around a client instance + with this. + + Example:: + + import dagger + from dagger import dag + + + async def main(): + async with dagger.connection(): + ctr = dag.container().from_("alpine") + + # Connection is closed when leaving the context manager's scope. + + + You can stream the logs from the engine to see progress:: + + import sys + import anyio + import dagger + from dagger import dag + + + async def main(): + cfg = dagger.Config(log_output=sys.stderr) + + async with dagger.connection(cfg): + ctr = dag.container().from_("python:3.11.1-alpine") + version = await ctr.with_exec(["python", "-V"]).stdout() + + print(version) + # Output: Python 3.11.1 + + + anyio.run(main) + """ + telemetry.initialize() + logger.debug("Establishing connection with shared client") + async with provision_engine(config or Config()) as engine: + conn = engine.get_shared_client_connection() + await engine.setup_client(conn) + yield conn + logger.debug("Closing connection with shared client") diff --git a/sdk/src/dagger/provisioning/_download.py b/sdk/src/dagger/provisioning/_download.py new file mode 100644 index 0000000..85eb1fe --- /dev/null +++ b/sdk/src/dagger/provisioning/_download.py @@ -0,0 +1,286 @@ +import contextlib +import dataclasses +import functools +import hashlib +import io +import logging +import os +import platform +import shutil +import tarfile +import tempfile +import typing +import zipfile +from collections.abc import Iterator +from pathlib import Path, PurePath +from typing import IO, ClassVar + +import anyio +import anyio.to_thread +import httpx +import platformdirs + +from dagger._engine._version import CLI_VERSION + +from ._exceptions import CLIReleaseUnavailableError, DownloadError +from ._progress import Progress + +logger = logging.getLogger(__name__) + +asyncify = anyio.to_thread.run_sync + + +class Platform(typing.NamedTuple): + os: str + arch: str + + +def get_platform() -> Platform: + normalized_arch = { + "x86_64": "amd64", + "aarch64": "arm64", + } + uname = platform.uname() + os_name = uname.system.lower() + arch = uname.machine.lower() + arch = normalized_arch.get(arch, arch) + return Platform(os_name, arch) + + +class TempFile(contextlib.AbstractContextManager): + """Create a temporary file that only deletes on error.""" + + def __init__(self, prefix: str, directory: Path): + super().__init__() + self.prefix = prefix + self.dir = directory + self.stack = contextlib.ExitStack() + + def __enter__(self) -> typing.IO[bytes]: + with self.stack as stack: + self.file = stack.enter_context( + tempfile.NamedTemporaryFile( + mode="a+b", + prefix=self.prefix, + dir=self.dir, + delete=False, + ), + ) + self.stack = stack.pop_all() + return self.file + + def __exit__(self, exc, *_) -> None: + self.stack.close() + # delete on error + if exc: + Path(self.file.name).unlink() + + +class StreamReader(IO[bytes]): + """File-like object from an httpx.Response.""" + + def __init__(self, response: httpx.Response, bufsize: int = tarfile.RECORDSIZE): + self.bufsize = bufsize + self.stream = response.iter_raw(bufsize) + self.hasher = hashlib.sha256() + + def read(self, size: int): + """Read chunk from stream.""" + # To satisfy the file-like api we should be able to read an arbitrary + # number of bytes from the stream, but the http response returns a + # generator with a fixed chunk size. No need to go lower level to change it + # since we know `read` will be called with the same size during extraction. + assert size == self.bufsize + try: + chunk = next(self.stream) + except StopIteration: + return None + self.hasher.update(chunk) + return chunk + + def readall(self): + """Read everything in stream while discarding chunks.""" + while self.read(self.bufsize): + ... + + def getbuffer(self): + """Read the entire stream into an in-memory buffer.""" + buf = io.BytesIO() + shutil.copyfileobj(self, buf, self.bufsize) + buf.seek(0) + return buf + + @property + def checksum(self) -> str: + return self.hasher.hexdigest() + + +@dataclasses.dataclass +class Downloader: + """Download the dagger CLI binary.""" + + CLI_BASE_URL: ClassVar[str] = "https://dl.dagger.io" + CLI_BIN_PREFIX: ClassVar[str] = "dagger-" + + version: str = CLI_VERSION + platform: Platform = dataclasses.field(default_factory=get_platform, kw_only=True) + progress: Progress = dataclasses.field(default_factory=Progress, kw_only=True) + + def _create_url(self, file_name: str): + return httpx.URL( + self.CLI_BASE_URL, + path=f"/dagger/releases/{self.version}/{file_name}", + ) + + @property + def archive_url(self): + ext = "zip" if self.platform.os == "windows" else "tar.gz" + return self._create_url( + f"dagger_v{self.version}_{self.platform.os}_{self.platform.arch}.{ext}" + ) + + @property + def archive_name(self): + return PurePath(self.archive_url.path).name + + @property + def checksum_url(self): + return self._create_url(self.checksum_name) + + @property + def checksum_name(self): + return "checksums.txt" + + @functools.cached_property + def cache_dir(self) -> Path: + # Use the XDG_CACHE_HOME environment variable in all platforms to follow + # https://github.com/adrg/xdg a bit more closely (used in the Go SDK). + # See https://github.com/dagger/dagger/issues/3963 + env = os.getenv("XDG_CACHE_HOME", "").strip() + path = Path(env).expanduser() if env else platformdirs.user_cache_path() + cache_dir = path / "dagger" + cache_dir.mkdir(mode=0o700, parents=True, exist_ok=True) + return cache_dir + + def __await__(self): + return self.get().__await__() + + async def get(self) -> str: + # TODO: Convert download to async. + return await asyncify(self.get_sync) + + def get_sync(self) -> str: + """Download CLI to cache and return its path.""" + cli_bin_path = self.cache_dir / f"{self.CLI_BIN_PREFIX}{self.version}" + + if self.platform.os == "windows": + cli_bin_path = cli_bin_path.with_suffix(".exe") + + if not cli_bin_path.exists(): + cli_bin_path = self._download(cli_bin_path) + + # garbage collection of old binaries + for file in self.cache_dir.glob(f"{self.CLI_BIN_PREFIX}*"): + if file != cli_bin_path: + file.unlink(missing_ok=True) + + return str(cli_bin_path.absolute()) + + def _download(self, path: Path) -> Path: + logger.debug("Downloading dagger CLI from %s to %s", self.archive_url, path) + try: + expected_hash = self.expected_checksum() + except httpx.HTTPError as e: + msg = f"Failed to download checksums from {self.checksum_url}: {e}" + raise DownloadError(msg) from e + + self.progress.update_sync("Downloading dagger CLI") + + with TempFile(f"temp-{self.CLI_BIN_PREFIX}", self.cache_dir) as tmp_bin: + try: + actual_hash = self.extract_cli_archive(tmp_bin) + except httpx.HTTPError as e: + msg = f"Failed to download archive from {self.archive_url}: {e}" + raise DownloadError(msg) from e + + if actual_hash != expected_hash: + msg = ( + f"Downloaded CLI binary checksum ({actual_hash}) " + f"does not match expected checksum ({expected_hash})" + ) + raise DownloadError(msg) + + tmp_bin_path = Path(tmp_bin.name) + tmp_bin_path.chmod(0o700) + return tmp_bin_path.rename(path) + + def expected_checksum(self) -> str: + archive_name = self.archive_name + with httpx.stream("GET", self.checksum_url, follow_redirects=True) as r: + try: + r.raise_for_status() + except httpx.HTTPStatusError as e: + if self.is_cli_release_unavailable(e.response.status_code): + msg = f"Failed to download checksums from {self.checksum_url}: {e}" + raise CLIReleaseUnavailableError(msg) from e + raise + for line in r.iter_lines(): + checksum, filename = line.split() + if filename == archive_name: + return checksum + msg = "Could not find checksum for archive" + raise DownloadError(msg) + + @staticmethod + def is_cli_release_unavailable(status_code: int) -> bool: + # dl.dagger.io returns 403 for missing S3 objects. + return status_code in (httpx.codes.FORBIDDEN, httpx.codes.NOT_FOUND) + + def extract_cli_archive(self, dest: IO[bytes]) -> str: + """ + Download the CLI archive and extract the binary into the provided dest. + + Returns + ------- + str + The sha256 hash of the whole archive as read during download. + """ + url = self.archive_url + + with httpx.stream("GET", url, follow_redirects=True) as r: + r.raise_for_status() + reader = StreamReader(r) + extractor = ( + self._extract_from_zip + if url.path.endswith(".zip") + else self._extract_from_tar + ) + + with extractor(reader) as cli_bin: + shutil.copyfileobj(cli_bin, dest) + + return reader.checksum + + @contextlib.contextmanager + def _extract_from_tar(self, reader: StreamReader) -> Iterator[IO[bytes]]: + with tarfile.open(mode="|gz", fileobj=reader) as tar: + for member in tar: + if member.name == "dagger" and (file := tar.extractfile(member)): + yield file + # ensure the entire body is read into the hash + reader.readall() + break + else: + msg = "There is no item named 'dagger' in the archive" + raise DownloadError(msg) + + @contextlib.contextmanager + def _extract_from_zip(self, reader: StreamReader) -> Iterator[IO[bytes]]: + # TODO: extract from stream instead of loading archive into memory + with zipfile.ZipFile(reader.getbuffer()) as zar: + try: + with zar.open("dagger.exe") as file: + yield file + except KeyError as e: + msg = "There is no item named 'dagger.exe' in the archive" + raise DownloadError(msg) from e diff --git a/sdk/src/dagger/provisioning/_engine.py b/sdk/src/dagger/provisioning/_engine.py new file mode 100644 index 0000000..d81980f --- /dev/null +++ b/sdk/src/dagger/provisioning/_engine.py @@ -0,0 +1,170 @@ +import contextlib +import logging +import os +import shutil +import sys +import typing +from typing import TextIO + +from exceptiongroup import ExceptionGroup +from typing_extensions import Self + +import dagger +from dagger._engine._version import CLI_VERSION +from dagger.client._session import ( + BaseConnection, + ConnectConfig, + ConnectParams, + SharedConnection, + SingleConnection, +) + +from ._config import Config +from ._download import Downloader +from ._exceptions import CLIReleaseUnavailableError, ProvisionError +from ._progress import Progress +from ._session import start_cli_session + +logger = logging.getLogger(__name__) + +if typing.TYPE_CHECKING: + from dagger import Client + + +@contextlib.asynccontextmanager +async def provision_engine(cfg: Config): + """Provision a new engine session.""" + async with contextlib.AsyncExitStack() as stack: + logger.debug("Provisioning engine") + yield await Engine(cfg, stack).provision() + logger.debug("Closing engine provisioning") + + +def fallback_to_local_cli( + download_error: Exception, + log_output: TextIO | None = None, +) -> str: + if not isinstance(download_error, CLIReleaseUnavailableError): + raise download_error + + bin_path = shutil.which("dagger") + if bin_path is None: + path_error = FileNotFoundError("dagger executable was not found") + msg = f"{download_error}\ndagger CLI not found in PATH: {path_error}" + raise ExceptionGroup(msg, [download_error, path_error]) from path_error + + warning_output = log_output if log_output is not None else sys.stderr + print( + f"CLI version {CLI_VERSION} is unavailable; using {bin_path} from PATH " + "(version compatibility is not guaranteed).", + file=warning_output, + ) + return bin_path + + +class Engine: + """Start engine session, provisioning if needed.""" + + def __init__(self, cfg: Config, stack: contextlib.AsyncExitStack) -> None: + super().__init__() + self.cfg = cfg + self.stack = stack + self.progress = Progress(cfg.console) + self.connect_params = None + self.connect_config = None + self.has_provisioned = False + + async def provision(self) -> Self: + connect_params = ConnectParams.from_env() + + if connect_params and self.cfg.workdir: + msg = ( + "Cannot configure workdir for existing session " + "(please use --workdir or host.directory " + "with absolute paths instead)." + ) + raise ProvisionError(msg) + + if not connect_params: + self.has_provisioned = True + # Only start progress if we are provisioning, not on active sessions + # like `dagger run`. + await self.progress.start("Provisioning engine") + download_error = None + try: + cli_bin = await self.get_cli() + except CLIReleaseUnavailableError as e: + download_error = e + cli_bin = fallback_to_local_cli(e, self.cfg.log_output) + + await self.progress.update("Creating new Engine session") + try: + connect_params = await self.stack.enter_async_context( + start_cli_session(self.cfg, cli_bin) + ) + except Exception as e: + if download_error is not None: + msg = ( + f"{download_error}\nfailed to use CLI from PATH " + f"{cli_bin!r}: {e}" + ) + raise ExceptionGroup(msg, [download_error, e]) from e + raise + + self.connect_params = connect_params + self.connect_config = ConnectConfig( + timeout=self.cfg.timeout, + retry=self.cfg.retry, + ) + + return self + + async def get_cli(self) -> str: + """Get path to CLI.""" + if cli_bin := os.getenv("_EXPERIMENTAL_DAGGER_CLI_BIN"): + return cli_bin + + # Get from cache or download. + return await Downloader(progress=self.progress) + + async def setup_client(self, conn: BaseConnection) -> "Client": + """Setup client instance from connection.""" + await self.progress.update("Establishing connection to the API server") + conn = await self.stack.enter_async_context(conn) + + client = dagger.Client.from_connection(conn) + self.stack.push_async_callback(self.progress.stop) + + return await self.verify(client) + + def get_shared_client_connection(self) -> SharedConnection: + """Global client connection to the GraphQL server.""" + assert self.connect_params + assert self.connect_config + return ( + SharedConnection() + .with_params(self.connect_params) + .with_config(self.connect_config) + ) + + def get_client_connection(self) -> SingleConnection: + """Isolated client connection to the GraphQL server.""" + assert self.connect_params + assert self.connect_config + return SingleConnection( + self.connect_params, + self.connect_config, + ) + + async def verify(self, client: "Client") -> "Client": + """Check if the Dagger CLI version is compatible with the engine.""" + await self.progress.update("Checking version compatibility") + try: + await client.version() + except dagger.QueryError as e: + logger.warning("Failed to check Dagger engine version compatibility: %s", e) + + await self.progress.update("Running pipelines") + await self.progress.stop() + + return client diff --git a/sdk/src/dagger/provisioning/_exceptions.py b/sdk/src/dagger/provisioning/_exceptions.py new file mode 100644 index 0000000..2f77783 --- /dev/null +++ b/sdk/src/dagger/provisioning/_exceptions.py @@ -0,0 +1,23 @@ +from dagger._exceptions import DaggerError + + +class ProvisionError(DaggerError): + """Error while provisioning the Dagger engine.""" + + +class DownloadError(ProvisionError): + """Error while downloading the Dagger CLI.""" + + def __str__(self) -> str: + return f"Failed to download the Dagger CLI: {super().__str__()}" + + +class CLIReleaseUnavailableError(DownloadError): + """The requested Dagger CLI release is unavailable.""" + + +class SessionError(ProvisionError): + """Error while starting an engine session.""" + + def __str__(self) -> str: + return f"Failed to start Dagger engine session: {super().__str__()}" diff --git a/sdk/src/dagger/provisioning/_progress.py b/sdk/src/dagger/provisioning/_progress.py new file mode 100644 index 0000000..feaee8a --- /dev/null +++ b/sdk/src/dagger/provisioning/_progress.py @@ -0,0 +1,38 @@ +import dataclasses + +import anyio.to_thread +import rich +from rich.console import Console +from rich.status import Status +from typing_extensions import Self + +asyncify = anyio.to_thread.run_sync + + +@dataclasses.dataclass(slots=True) +class Progress: + console: Console = dataclasses.field(default_factory=rich.get_console) + status: Status | None = dataclasses.field(default=None, init=False) + + async def start(self, status: str) -> None: + self.status = Status(status, console=self.console) + await asyncify(self.status.start) + + async def stop(self) -> None: + if self.status: + await asyncify(self.status.stop) + self.status = None + + async def __aenter__(self) -> Self: + return self + + async def __aexit__(self, *_) -> None: + await self.stop() + + async def update(self, message: str) -> None: + if self.status: + await asyncify(self.status.update, message) + + def update_sync(self, message: str) -> None: + if self.status: + self.status.update(message) diff --git a/sdk/src/dagger/provisioning/_session.py b/sdk/src/dagger/provisioning/_session.py new file mode 100644 index 0000000..96d7d30 --- /dev/null +++ b/sdk/src/dagger/provisioning/_session.py @@ -0,0 +1,292 @@ +import collections +import contextlib +import dataclasses +import json +import logging +import subprocess +import threading +import time +from collections.abc import Iterable +from importlib import metadata +from pathlib import Path +from typing import TextIO, cast + +from typing_extensions import Self + +from dagger._managers import SyncResource +from dagger.client._session import ConnectParams + +from ._config import Config +from ._exceptions import SessionError + +logger = logging.getLogger(__name__) + + +OS_ETXTBSY = 26 + + +def get_sdk_version(): + try: + return metadata.version("dagger-io") + except metadata.PackageNotFoundError: + return "n/a" + + +def start_cli_session(cfg: Config, path: str): + # TODO: Convert calling session subprocess to async. + return SyncResource(start_cli_session_sync(cfg, path)) + + +@dataclasses.dataclass(slots=True) +class Pclose(contextlib.AbstractContextManager): + """Close process by closing stdin and waiting for it to exit.""" + + proc: subprocess.Popen[str] + + # Set a long timeout to give time for any cache exports to pack layers up + # which currently has to happen synchronously with the session. + timeout: int = 300 + + def __exit__(self, exc_type, exc_value, traceback): + # Kill the child process by closing stdin, not via SIGKILL, so it has + # a chance to drain logs. + try: + if self.proc.stdin: + self.proc.stdin.close() + except AttributeError: + # FakeProcess doesn't have a stdin attribute (tests) + self.proc.terminate() + + try: + self._wait() + except Exception: # Including KeyboardInterrupt, wait handled that. + self.proc.kill() + # We don't call proc.wait() again as proc.__exit__ does that for us. + raise + + def _wait(self): + # avoids raise-within-try (TRY301) + if self.proc.wait(self.timeout): + # non-zero exit code + msg = make_process_error_msg(self.proc, None, None) + raise SessionError(msg) + + +@contextlib.contextmanager +def start_cli_session_sync(cfg: Config, path: str): + """Start an engine session with a provided CLI path.""" + logger.debug("Starting session using %s", path) + try: + with contextlib.ExitStack() as stack: + session = stack.enter_context(run(cfg, path)) + params = get_connect_params(session) + stack.push(Pclose(session.proc)) + yield params + except (OSError, ValueError, TypeError) as e: + raise SessionError(e) from e + + +def _has_fileno(stream: TextIO) -> bool: + """Check if a stream has a valid file descriptor.""" + try: + stream.fileno() + except (AttributeError, OSError): + return False + return True + + +def _forward_stderr(source: TextIO, dest: TextIO) -> None: + """Forward lines from source to dest until EOF.""" + try: + with contextlib.suppress(ValueError): + dest.writelines(source) + finally: + with contextlib.suppress(OSError, ValueError): + source.close() + + +class _TailBuffer: + """Append-only line buffer that keeps only the most recent lines. + + Used to drain the engine's stderr pipe when the user hasn't configured + log_output. Without a drain, the pipe buffer fills (~64 KB on Linux) and + the engine blocks writing logs, which deadlocks session shutdown. + """ + + def __init__(self, maxlines: int = 200) -> None: + self._lines: collections.deque[str] = collections.deque(maxlen=maxlines) + + def writelines(self, lines: Iterable[str]) -> None: + self._lines.extend(lines) + + def write(self, s: str) -> None: + self._lines.append(s) + + def getvalue(self) -> str: + return "".join(self._lines) + + +@dataclasses.dataclass(slots=True) +class _StartedSession(contextlib.AbstractContextManager): + """Bundle the dagger session subprocess with its background drain state. + + Avoids monkey-patching attributes onto :class:`subprocess.Popen` and gives + callers (`get_connect_params`, error reporting) a typed way to reach the + captured stderr. + """ + + proc: subprocess.Popen[str] + stderr_tail: _TailBuffer | None = None + stderr_thread: threading.Thread | None = None + + def __enter__(self) -> Self: + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.proc.__exit__(exc_type, exc_value, traceback) + + +def _build_session_args(cfg: Config, path: str) -> list[str]: + args = [ + path, + "session", + "--label", + "dagger.io/sdk.name:python", + "--label", + f"dagger.io/sdk.version:{get_sdk_version()}", + ] + if cfg.workdir: + args.extend(["--workdir", str(Path(cfg.workdir).absolute())]) + if cfg.config_path: + args.extend(["--project", str(Path(cfg.config_path).absolute())]) + if cfg.load_workspace_modules: + args.append("--load-workspace-modules") + return args + + +def _resolve_stderr( + log_output: TextIO | None, +) -> tuple[int | TextIO, TextIO | None]: + """Decide stderr destination and what (if anything) drains it. + + If log_output has a real file descriptor, we can let the child write to + it directly. Otherwise (StringIO, no log_output at all) we use a PIPE + and drain it from a background thread: without a drain, the ~64 KB pipe + buffer fills up and the engine blocks writing logs, which deadlocks + session shutdown. + """ + if log_output is not None and _has_fileno(log_output): + return log_output, None + drain_dest: TextIO = log_output if log_output is not None else _TailBuffer() + return subprocess.PIPE, drain_dest + + +def _spawn_with_etxtbsy_retry( + args: list[str], + stderr_target: int | TextIO, +) -> subprocess.Popen[str]: + """Start the session subprocess, retrying on ETXTBSY. + + The "text file busy" error can happen due to a flaw in how Linux works: + if any fork of this process happens while the temp binary file is open + for writing, a child process can still have it open for writing before + it calls exec. See https://github.com/golang/go/issues/22315 for context. + """ + for _ in range(10): + try: + return subprocess.Popen( # noqa: S603 + args, + bufsize=0, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=stderr_target, + encoding="utf-8", + ) + except OSError as e: # noqa: PERF203 + if e.errno != OS_ETXTBSY: + raise + logger.warning("file busy, retrying in 0.1 seconds...") + time.sleep(0.1) + msg = "CLI busy" + raise SessionError(msg) + + +def run(cfg: Config, path: str) -> _StartedSession: + args = _build_session_args(cfg, path) + stderr_target, drain_dest = _resolve_stderr(cfg.log_output) + proc = _spawn_with_etxtbsy_retry(args, stderr_target) + session = _StartedSession(proc=proc) + if drain_dest is not None and proc.stderr: + thread = threading.Thread( + target=_forward_stderr, + args=(proc.stderr, drain_dest), + daemon=True, + ) + thread.start() + session.stderr_thread = thread + if isinstance(drain_dest, _TailBuffer): + session.stderr_tail = drain_dest + # Forwarding thread now owns the pipe; clear proc.stderr so callers + # don't race with it over the same fd. + proc.stderr = None + return session + + +def _read_session_stderr(session: _StartedSession) -> str | None: + """Read whatever stderr we have for a session, if any. + + When stderr was piped without forwarding, we may read it directly. When + it was drained by the background thread into a tail buffer, we wait + briefly for the thread to finish flushing, then read from the buffer. + """ + proc = session.proc + if proc.stderr and proc.stderr.readable(): + return proc.stderr.read() + if session.stderr_tail is None: + return None + if session.stderr_thread is not None: + session.stderr_thread.join(timeout=1.0) + return session.stderr_tail.getvalue() + + +def get_connect_params(session: _StartedSession) -> ConnectParams: + # TODO: implement engine session timeout (self.cfg.engine_timeout?) + proc = session.proc + assert proc.stdout + conn = proc.stdout.readline() + + # Check if subprocess exited with an error + if proc.poll(): + stdout = conn + proc.stdout.read() + stderr = _read_session_stderr(session) + msg = make_process_error_msg(proc, stdout, stderr) + raise SessionError(msg) + + if not conn: + msg = "No connection params" + raise SessionError(msg) + + try: + return ConnectParams(**json.loads(conn)) + except (ValueError, TypeError) as e: + msg = f"Invalid connection params: {conn}" + raise SessionError(msg) from e + + +def make_process_error_msg( + proc: subprocess.Popen[str], + stdout: str | None, + stderr: str | None, +) -> str: + args = cast(list[str], proc.args) + + # Reuse error message from CalledProcessError + exc = subprocess.CalledProcessError(proc.returncode, " ".join(args)) + + msg = str(exc) + detail = stderr or stdout + if detail and detail.strip(): + # `msg` ends in a period, just append + msg = f"{msg} {detail.strip()}" + + return msg diff --git a/sdk/src/dagger/py.typed b/sdk/src/dagger/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/sdk/src/dagger/telemetry.py b/sdk/src/dagger/telemetry.py new file mode 100644 index 0000000..992ca9a --- /dev/null +++ b/sdk/src/dagger/telemetry.py @@ -0,0 +1,212 @@ +import logging +import os +from typing import Final + +from opentelemetry import context, propagate, trace +from opentelemetry.environment_variables import OTEL_PYTHON_TRACER_PROVIDER +from opentelemetry.instrumentation.logging import LoggingInstrumentor +from opentelemetry.instrumentation.logging.environment_variables import ( + OTEL_PYTHON_LOG_CORRELATION, + OTEL_PYTHON_LOG_FORMAT, + OTEL_PYTHON_LOG_LEVEL, +) +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, +) +from opentelemetry.sdk.environment_variables import ( + OTEL_EXPORTER_OTLP_ENDPOINT, + OTEL_EXPORTER_OTLP_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.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(*, debug: bool = False): + """Configure telemetry. + + If debug is True, enables console exporters. + """ + _DaggerPropagationConfigurator().configure() + _DaggerOtelConfigurator().configure(debug=debug) + + +def get_tracer() -> trace.Tracer: + """Returns a tracer to use with Dagger.""" + initialize() + return trace.get_tracer("dagger.io/sdk.python") + + +def shutdown(): + """Process all spans that have not yet been processed.""" + # TODO: set a timeout + + tracer_provider = get_tracer_provider() + # Provider shutdown is called automatically on exit, we just need the forced + # flush but might as well shutdown now too. + + if isinstance(tracer_provider, sdktrace.TracerProvider): + tracer_provider.force_flush() + tracer_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): + exporters = ("otlp",) + + # 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 + + if kwargs.get("debug"): + self.exporters += ("console",) + + 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") + + # Logging instrumentation. + os.environ.setdefault(OTEL_PYTHON_LOG_CORRELATION, "true") + os.environ.setdefault(OTEL_PYTHON_LOG_LEVEL, "warning") + os.environ.setdefault( + OTEL_PYTHON_LOG_FORMAT, + "%(levelname)s [%(name)s]: %(message)s", + ) + + _vars = { + OTEL_EXPORTER_OTLP_ENDPOINT: OTEL_EXPORTER_OTLP_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: Dagger's engine only accepts Gauge metrics today (engine-side + # exec resource monitoring). Emitting counters/histograms from modules + # produces 500s and retry noise, so skip metric initialization by + # passing an empty list for the metrics slot of _import_exporters. + # + # Also skip OTel log exporting. Dagger already captures module + # stdout/stderr and forwards it as logs; installing OTel's logging + # handler would duplicate Python logging records in the TUI. + trace_exporters, _, _ = _import_exporters( + _get_exporter_names("traces"), + [], + [], + ) + logger.debug( + "Initializing traces telemetry with exporters: %s", + ", ".join(trace_exporters) if trace_exporters else "none", + ) + _init_tracing(trace_exporters) + + # The logging instrumentor injects the trace context into logs without + # exporting them separately. + LoggingInstrumentor().instrument() diff --git a/sdk/tests/codegen/test_generator.py b/sdk/tests/codegen/test_generator.py new file mode 100644 index 0000000..e83b20a --- /dev/null +++ b/sdk/tests/codegen/test_generator.py @@ -0,0 +1,578 @@ +from textwrap import dedent + +import graphql +import pytest +from graphql import ( + GraphQLArgument as Argument, +) +from graphql import ( + GraphQLBoolean as Boolean, +) +from graphql import ( + GraphQLEnumType, + GraphQLEnumValue, + GraphQLID, + build_schema, +) +from graphql import ( + GraphQLField as Field, +) +from graphql import ( + GraphQLInputField as Input, +) +from graphql import ( + GraphQLInputField as InputField, +) +from graphql import ( + GraphQLInputObjectType as InputObject, +) +from graphql import ( + GraphQLInt as Int, +) +from graphql import ( + GraphQLInterfaceType as Interface, +) +from graphql import ( + GraphQLList as List, +) +from graphql import ( + GraphQLNonNull as NonNull, +) +from graphql import ( + GraphQLObjectType as Object, +) +from graphql import ( + GraphQLScalarType as Scalar, +) +from graphql import ( + GraphQLString as String, +) + +from codegen.generator import ( + Context, + _InputField, + _ObjectField, + doc, + format_input_type, + format_name, + format_output_type, + generate, +) +from codegen.generator import ( + Enum as EnumHandler, +) +from codegen.generator import ( + Input as InputHandler, +) +from codegen.generator import ( + Scalar as ScalarHandler, +) + +# Schema with @expectedType directive for testing unified ID behavior. +_EXPECTED_TYPE_SCHEMA = build_schema(""" + directive @expectedType(name: String!) + on FIELD_DEFINITION + | ARGUMENT_DEFINITION + | INPUT_FIELD_DEFINITION + type Foo { sync: ID! @expectedType(name: "Foo") } + type Secret { plaintext: String! } + type Query { + fn(secret: ID! @expectedType(name: "Secret")): String + fn2(secret: ID @expectedType(name: "Secret")): String + } +""") + + +@pytest.fixture +def ctx(): + return Context( + schema=_EXPECTED_TYPE_SCHEMA, + ids=frozenset({"ID"}), + remaining={"Secret"}, + ) + + +@pytest.mark.parametrize( + ("graphql", "expected"), + [ + ("stdout", "stdout"), + ("envVariable", "env_variable"), # casing + ("from", "from_"), # reserved keyword + ("str", "str_"), # builtin + ("withFS", "with_fs"), # initialism + ], +) +def test_format_name(graphql, expected): + assert format_name(graphql) == expected + + +opts = InputObject( + "Options", + fields={ + "key": InputField(NonNull(Scalar("CacheVolumeID"))), + "name": InputField(String), + }, +) + + +@pytest.mark.parametrize( + ("graphql", "expected", "expected_type"), + [ + (NonNull(List(NonNull(String))), "list[str]", None), + (List(String), "list[str | None] | None", None), + (List(NonNull(String)), "list[str] | None", None), + (NonNull(GraphQLID), "File", "File"), + (GraphQLID, "File | None", "File"), + (NonNull(opts), "Options", None), + (opts, "Options | None", None), + (NonNull(List(NonNull(opts))), "list[Options]", None), + (NonNull(List(opts)), "list[Options | None]", None), + (List(NonNull(opts)), "list[Options] | None", None), + (List(opts), "list[Options | None] | None", None), + ], +) +def test_format_input_type(graphql, expected, expected_type): + assert format_input_type(graphql, expected_type=expected_type) == expected + + +cache_volume = Object( + "CacheVolume", + fields={ + "id": Field( + NonNull(Scalar("CacheVolumeID")), + {}, + ), + }, +) + + +@pytest.mark.parametrize( + ("graphql", "expected"), + [ + (NonNull(List(NonNull(String))), "list[str]"), + (List(String), "list[str | None] | None"), + (List(NonNull(String)), "list[str] | None"), + (NonNull(Scalar("FileID")), "FileID"), + (Scalar("FileID"), "FileID | None"), + (NonNull(cache_volume), "CacheVolume"), + (cache_volume, "CacheVolume"), + (List(NonNull(cache_volume)), "list[CacheVolume]"), + (List(cache_volume), "list[CacheVolume | None]"), + ], +) +def test_format_output_type(graphql, expected): + assert format_output_type(graphql) == expected + + +def _(type_: graphql.GraphQLInputType, default_value: str): + """Read default value from JSON result the same way that graphql library does.""" + return type_, graphql.value_from_ast(graphql.parse_value(default_value), type_) + + +@pytest.mark.parametrize( + ("name", "args", "expected"), + [ + ("args", (NonNull(List(String)),), "args: list[str | None]"), + # Secret ID test cases moved to test_input_field_param_expected_type + # since they require @expectedType directive on the AST node. + ("from", _(String, "null"), "from_: str | None = None"), + ("lines", _(Int, "1"), "lines: int | None = 1"), + ( + "configPath", + _(NonNull(String), '"/dagger.json"'), + "config_path: str = '/dagger.json'", + ), + # Go example: // +default="foo bar" -> "defaultValue": "\"foo bar\"" + ("space", _(String, '"foo bar"'), "space: str | None = 'foo bar'"), + # Go example: // +default='foo bar' -> "defaultValue": "\"'foo bar'\"" + ( + "singleQuotes", + _(String, "\"'foo bar'\""), + "single_quotes: str | None = \"'foo bar'\"", + ), + # Go example: // +default=`foo bar` -> "defaultValue": "\"`foo bar`\"" + ("backticks", _(String, '"`foo bar`"'), "backticks: str | None = '`foo bar`'"), + ], +) +@pytest.mark.parametrize("cls", [Argument, Input]) +def test_input_field_param(cls, name: str, args, expected: str, ctx: Context): + assert _InputField(ctx, name, cls(*args)).as_param() == expected + + +def test_input_field_param_expected_type(ctx: Context): + """Test that ID args with @expectedType resolve to the object type name.""" + query_type = _EXPECTED_TYPE_SCHEMA.type_map["Query"] + # Required: secret: ID! @expectedType(name: "Secret") + required_arg = query_type.fields["fn"].args["secret"] + assert _InputField(ctx, "secret", required_arg).as_param() == "secret: Secret" + # Optional: secret: ID @expectedType(name: "Secret") + optional_arg = query_type.fields["fn2"].args["secret"] + result = _InputField(ctx, "secret", optional_arg).as_param() + assert result == "secret: Secret | None = None" + + +@pytest.mark.parametrize( + ("name", "args", "expected"), + [ + ( + "context", + (NonNull(Scalar("DirectoryID")),), + 'Arg("context", context),', + ), + ( + "secret", + (Scalar("SecretID"),), + 'Arg("secret", secret, None),', + ), + ( + "lines", + (Int, 1), + 'Arg("lines", lines, 1),', + ), + ( + "from", + (String, None), + 'Arg("from", from_, None),', + ), + ( + "configPath", + (NonNull(String), "/dagger.json"), + "Arg(\"configPath\", config_path, '/dagger.json'),", + ), + ], +) +@pytest.mark.parametrize("cls", [Argument, Input]) +def test_input_field_arg(cls, name, args, expected, ctx: Context): + assert _InputField(ctx, name, cls(*args)).as_arg() == expected + + +def test_input_object_field_deprecated(): + local_ctx = Context() + input_type = InputObject( + "LegacyInput", + lambda: { + "legacyField": InputField( + String, + description="Legacy config path.", + deprecation_reason="Use `configPath` instead.", + ), + "active": InputField(Boolean), + }, + description="Configuration options.", + ) + + rendered = InputHandler(local_ctx).render(input_type) + + assert "class LegacyInput(Input):" in rendered + assert "legacy_field: str | None = None" in rendered + assert ".. deprecated:: Use config_path instead." in rendered + + +def test_core_sync(ctx: Context): + # Use the field from the parsed schema so it has the @expectedType AST node. + foo_type = _EXPECTED_TYPE_SCHEMA.type_map["Foo"] + sync_field = foo_type.fields["sync"] + handler = _ObjectField( + ctx, + "sync", + sync_field, + foo_type, + ) + + assert handler.func_signature() == "async def sync(self) -> Self:" + + assert str(handler.func_body()).endswith( + 'return await self._ctx.execute_sync(self, "sync", _args)' + ) + + +def test_generate_modern_id_surface(): + schema = build_schema( + """ + directive @expectedType(name: String!) + on FIELD_DEFINITION + | ARGUMENT_DEFINITION + | INPUT_FIELD_DEFINITION + + type Container { id: ID! @expectedType(name: "Container") } + type Query { container: Container! } + """ + ) + + code = generate(schema, schema_version="v0.21.0-dev") + + assert "class ContainerID(Scalar):" not in code + assert "load_container_from_id" not in code + assert "async def id(self) -> str:" in code + assert "return await _ctx.execute(str)" in code + + +def test_user_sync_leaf(ctx: Context): + handler = _ObjectField( + ctx, + "sync", + Field(NonNull(String), {}), + Object("Foo", {}), + ) + + assert handler.func_signature() == "async def sync(self) -> str:" + + assert str(handler.func_body()).endswith( + dedent( + """ + _args: list[Arg] = [] + _ctx = self._select("sync", _args) + return await _ctx.execute(str) + """.rstrip() + ) + ) + + +def test_user_sync_object(ctx: Context): + handler = _ObjectField( + ctx, + "sync", + Field(NonNull(Object("Foo", {})), {}), + Object("Foo", {}), + ) + assert str(handler) == dedent( + """ + def sync(self) -> Self: + _args: list[Arg] = [] + _ctx = self._select("sync", _args) + return Foo(_ctx) + """.rstrip() + ) + + +def test_func_doc_deprecated_args(ctx: Context): + field = Field( + String, + { + "path": Argument(NonNull(String)), + "configDir": Argument( + String, + deprecation_reason="Use `configPath` instead.", + ), + }, + deprecation_reason="Use apply_config instead.", + ) + + parent = Object("Container", lambda: {"apply": field}) + handler = _ObjectField(ctx, "apply", field, parent) + + docstring = handler.func_doc() + + assert "Parameters" in docstring + assert ".. deprecated::\n Use apply_config instead." in docstring + doc_lines = {line.strip() for line in docstring.splitlines()} + assert "config_dir:" in doc_lines + assert ".. deprecated:: Use config_path instead." in doc_lines + + body = handler.func_body() + normalized = body.replace('\\"', '"') + assert 'Method "apply" is deprecated: Use apply_config instead.' in normalized + + +def test_interface_methods_deprecated(ctx: Context): + iface = Interface( + "Fooer", + lambda: { + "foo": Field( + String, + { + "value": Argument( + Int, + deprecation_reason="Use `other` instead.", + ) + }, + deprecation_reason="Call `bar` instead.", + ), + "bar": Field( + String, + {"note": Argument(String, description="Caller note.")}, + ), + }, + ) + + foo_handler = _ObjectField(ctx, "foo", iface.fields["foo"], iface) + foo_doc = foo_handler.func_doc() + assert ".. deprecated::\n Call :py:meth:`bar` instead." in foo_doc + + foo_doc_lines = {line.strip() for line in foo_doc.splitlines()} + assert ".. deprecated:: Use other instead." in foo_doc_lines + + foo_body = foo_handler.func_body() + foo_normalized = foo_body.replace('\\"', '"') + assert 'Method "foo" is deprecated: Call "bar" instead.' in foo_normalized + + bar_handler = _ObjectField(ctx, "bar", iface.fields["bar"], iface) + bar_doc = bar_handler.func_doc() + assert ".. deprecated::" not in bar_doc + assert "Parameters" in bar_doc + + bar_body = bar_handler.func_body() + assert "warnings.warn" not in bar_body + + +@pytest.mark.parametrize( + ("type_", "expected"), + [ + (GraphQLID, False), + (String, False), + (Int, False), + (Scalar("FileID"), True), + (Object("Container", {}), False), + ], +) +def test_scalar_predicate(type_, expected, ctx: Context): + assert ScalarHandler(ctx).predicate(type_) is expected + + +@pytest.mark.parametrize( + ("type_", "expected"), + [ + # with doc + ( + Scalar("SecretID", description="A unique identifier for a secret."), + dedent( + ''' + class SecretID(Scalar): + """A unique identifier for a secret.""" + ''', + ), + ), + # without doc + ( + Scalar("FileID"), + dedent( + """ + class FileID(Scalar): + ... + """, + ), + ), + ], +) +def test_scalar_render(type_, expected, ctx: Context): + handler = ScalarHandler(ctx) + assert handler.render(type_) == expected + + +@pytest.mark.parametrize( + ("type_", "expected"), + [ + # with doc + ( + GraphQLEnumType( + "Enumeration", + { + "ONE": GraphQLEnumValue("ONE", description="First value."), + "TWO": GraphQLEnumValue("TWO", description="Second value."), + "THREE": GraphQLEnumValue("THREE", description="Third value."), + }, + description="Example of an enumeration.", + ), + dedent( + ''' + class Enumeration(Enum): + """Example of an enumeration.""" + + ONE = 'ONE' + """First value.""" + + THREE = 'THREE' + """Third value.""" + + TWO = 'TWO' + """Second value.""" + ''', + ), + ), + # without doc + ( + GraphQLEnumType( + "Enumeration", + { + "ONE": GraphQLEnumValue("ONE"), + "TWO": GraphQLEnumValue("TWO"), + "THREE": GraphQLEnumValue("THREE"), + }, + ), + dedent( + """ + class Enumeration(Enum): + + ONE = 'ONE' + + THREE = 'THREE' + + TWO = 'TWO' + """, + ), + ), + ( + GraphQLEnumType( + "Mode", + { + "VALUE": GraphQLEnumValue( + "VALUE", + deprecation_reason="Use ModeV2 instead.", + ), + }, + ), + dedent( + ''' + class Mode(Enum): + + VALUE = 'VALUE' + """.. deprecated:: Use ModeV2 instead.""" + ''', + ), + ), + ], +) +def test_enum_render(type_, expected, ctx: Context): + handler = EnumHandler(ctx) + assert handler.render(type_) == expected + + +@pytest.mark.parametrize( + ("original", "expected"), + [ + ( + "Lorem ipsum dolores est.", + '"""Lorem ipsum dolores est."""', + ), + ( + "Lorem ipsum dolores est.\n\nSecond paragraph.", + dedent( + '''\ + """Lorem ipsum dolores est. + + Second paragraph. + """''', + ), + ), + ( + '"Foo": bar.', + r'""""Foo": bar."""', + ), + ( + 'Example: "foobar"', + r'"""Example: "foobar" """', + ), + ( + 'Lorem ipsum dolores est.\n\nExample: "foobar"', + dedent( + '''\ + """Lorem ipsum dolores est. + + Example: "foobar" + """''', + ), + ), + ], +) +def test_doc(original: str, expected: str): + assert doc(original) == expected diff --git a/sdk/tests/conftest.py b/sdk/tests/conftest.py new file mode 100644 index 0000000..1e402f0 --- /dev/null +++ b/sdk/tests/conftest.py @@ -0,0 +1,25 @@ +import pytest + + +@pytest.fixture(scope="session", autouse=True) +def setup_telemetry(): + import dagger.telemetry + + dagger.telemetry.initialize() + + +@pytest.fixture(scope="session") +def anyio_backend(): + # TODO: remove when other backends can be supported + # (i.e., HTTPX transport since it supports AnyIO) + return "asyncio" + + +@pytest.fixture +def alpine_version(): + return "3.20.2" + + +@pytest.fixture +def alpine_image(alpine_version): + return f"alpine:{alpine_version}" diff --git a/sdk/tests/mod/test_enum_docstrings.py b/sdk/tests/mod/test_enum_docstrings.py new file mode 100644 index 0000000..03fdf7f --- /dev/null +++ b/sdk/tests/mod/test_enum_docstrings.py @@ -0,0 +1,71 @@ +import enum + +import pytest + +import dagger +from dagger.mod._utils import extract_enum_member_doc + + +class ExampleEnum(enum.Enum): + FIRST = "first" + "This is the first option" + + SECOND = "second" + "This is the second option" + + THIRD = "third" + # No docstring for this one + + +def test_extract_enum_member_doc(): + """Test that we can extract docstrings from enum members using AST parsing.""" + docs = extract_enum_member_doc(ExampleEnum) + + first = docs.get("FIRST") + assert first is not None + assert first.description == "This is the first option" + assert first.deprecated is None + + second = docs.get("SECOND") + assert second is not None + assert second.description == "This is the second option" + assert second.deprecated is None + + assert "THIRD" not in docs # No docstring for THIRD + + +class EmptyEnum(enum.Enum): + VALUE = "value" + + +def test_extract_enum_member_doc_no_docs(): + """Test that we handle enums with no member docstrings gracefully.""" + docs = extract_enum_member_doc(EmptyEnum) + + assert docs == {} + + +class DeprecatedExample(enum.Enum): + ALPHA = "alpha" + """Alpha value. + + .. deprecated:: 1.2 + Use beta instead. + Remove no later than 2.0. + """ + + +def test_extract_enum_member_doc_with_deprecated_directive(): + docs = extract_enum_member_doc(DeprecatedExample) + + meta = docs["ALPHA"] + assert meta.description == "Alpha value." + assert meta.deprecated == "1.2\nUse beta instead.\nRemove no later than 2.0." + + +def test_enum_deprecation(): + with pytest.warns(DeprecationWarning, match="Use 'enum.Enum' instead"): + + class DeprecatedEnum(dagger.Enum): + FIRST = "first", "This is the first option" + SECOND = "second", "This is the second option" diff --git a/sdk/tests/mod/test_forward_reference.py b/sdk/tests/mod/test_forward_reference.py new file mode 100644 index 0000000..531c3c2 --- /dev/null +++ b/sdk/tests/mod/test_forward_reference.py @@ -0,0 +1,24 @@ +from typing_extensions import Self + +from dagger.mod import Module + +mod = Module() + + +@mod.object_type +class Foo: + @mod.function + def method1(self) -> "Foo": ... + + @mod.function + def method2(self) -> Self: ... + + +def test_method_returns_resolved_forward_reference(): + fn = mod.get_object("Foo").functions["method1"] + assert fn.return_type is Foo + + +def test_method_returns_resolved_self(): + fn = mod.get_object("Foo").functions["method2"] + assert fn.return_type is Foo diff --git a/sdk/tests/mod/test_future_annotations.py b/sdk/tests/mod/test_future_annotations.py new file mode 100644 index 0000000..e908186 --- /dev/null +++ b/sdk/tests/mod/test_future_annotations.py @@ -0,0 +1,112 @@ +"""Tests for annotations with `from __future__ import annotations`. + +This file MUST have `from __future__ import annotations` at the top to test +the issue where annotations don't work when they are stringified. +See: https://github.com/dagger/dagger/issues/11554 +""" + +from __future__ import annotations + +from typing import Annotated + +from typing_extensions import Doc + +import dagger +from dagger import DefaultPath, Deprecated, Ignore, Name +from dagger.mod import Module + + +def test_default_path_with_future_annotations(): + """Test that DefaultPath works with `from __future__ import annotations`.""" + mod = Module() + + @mod.object_type + class Foo: + @mod.function + def build( + self, + src: Annotated[dagger.Directory, DefaultPath(".")], + ) -> str: + return "ok" + + fn = mod.get_object("Foo").functions["build"] + param = fn.parameters["src"] + + assert param.default_path == "." + assert param.is_optional is True + + +def test_doc_with_future_annotations(): + """Test that Doc works with `from __future__ import annotations`.""" + mod = Module() + + @mod.object_type + class Foo: + @mod.function + def build( + self, + src: Annotated[str, Doc("Source directory")], + ) -> str: + return "ok" + + fn = mod.get_object("Foo").functions["build"] + param = fn.parameters["src"] + + assert param.doc == "Source directory" + + +def test_name_with_future_annotations(): + """Test that Name works with `from __future__ import annotations`.""" + mod = Module() + + @mod.object_type + class Foo: + @mod.function + def build( + self, + src: Annotated[str, Name("source")], + ) -> str: + return "ok" + + fn = mod.get_object("Foo").functions["build"] + param = fn.parameters["src"] + + assert param.name == "source" + + +def test_ignore_with_future_annotations(): + """Test that Ignore works with `from __future__ import annotations`.""" + mod = Module() + + @mod.object_type + class Foo: + @mod.function + def build( + self, + src: Annotated[dagger.Directory, Ignore(["*.tmp", ".git"])], + ) -> str: + return "ok" + + fn = mod.get_object("Foo").functions["build"] + param = fn.parameters["src"] + + assert param.ignore == ["*.tmp", ".git"] + + +def test_deprecated_with_future_annotations(): + """Test that Deprecated works with `from __future__ import annotations`.""" + mod = Module() + + @mod.object_type + class Foo: + @mod.function + def build( + self, + src: Annotated[str, Deprecated("Use new_src instead")] = "", + ) -> str: + return "ok" + + fn = mod.get_object("Foo").functions["build"] + param = fn.parameters["src"] + + assert param.deprecated == "Use new_src instead" diff --git a/sdk/tests/mod/test_interfaces.py b/sdk/tests/mod/test_interfaces.py new file mode 100644 index 0000000..31b99d0 --- /dev/null +++ b/sdk/tests/mod/test_interfaces.py @@ -0,0 +1,79 @@ +import typing + +import pytest +from typing_extensions import Self + +import dagger +from dagger.client.base import Interface +from dagger.mod import Module +from dagger.mod._utils import ( + is_dagger_interface_type, + is_dagger_object_type, +) + +pytestmark = [ + pytest.mark.anyio, +] + + +@pytest.fixture +def mod() -> Module: + m = Module("Pond") + + @m.interface + class Goose(typing.Protocol): + @m.function + def speak(self) -> str: ... + + @m.interface + class Duck(typing.Protocol): + @m.function + def quack(self) -> str: ... + + @m.function + def get_self(self) -> Self: ... + + @m.function + def get_mob(self) -> list[Self]: ... + + def get_private(self) -> bool: ... + + @m.object_type + class Pond: + duck: Duck = dagger.field() + + return m + + +@pytest.fixture +async def duck(mod: Module): + r, t = await mod.get_structured_result( + "Pond", + {"duck": "123456"}, + "duck", + {}, + ) + return r, t + + +def test_registered_functions(mod: Module): + assert "quack" in mod.get_object("Duck").functions + + +async def test_generated_implementation(duck): + r, t = duck + + assert t.return_type.__name__ == "Duck" + assert is_dagger_interface_type(t.return_type) + + assert type(r).__name__ == "PondDuck" + assert is_dagger_object_type(type(r)) + + assert isinstance(r, Interface) + assert hasattr(r, "quack") + assert not hasattr(r, "get_private") + + +async def test_interface_object_chain(duck): + r, _ = duck + assert type(r.get_self()) is type(r) diff --git a/sdk/tests/mod/test_registration.py b/sdk/tests/mod/test_registration.py new file mode 100644 index 0000000..ea903ba --- /dev/null +++ b/sdk/tests/mod/test_registration.py @@ -0,0 +1,249 @@ +import itertools +from typing import Annotated + +import pytest +from typing_extensions import Doc, Self + +import dagger +from dagger import dag +from dagger.mod import Module +from dagger.mod._converter import to_typedef +from dagger.mod._exceptions import BadUsageError + + +def test_object_type_resolvers(): + mod = Module() + + @mod.object_type + class ExposedClass: + private_field: str + exposed_field: str = mod.field() + + def private_method(self) -> str: ... + @mod.function + def exposed_method(self) -> str: ... + + def private_function() -> str: ... + @mod.function + def unsupported_top_level() -> str: ... + + fields = list( + itertools.chain.from_iterable( + (f.original_name for f in o.fields.values()) for o in mod._objects.values() + ) + ) + functions = list( + itertools.chain.from_iterable( + (f.original_name for f in o.functions.values()) + for o in mod._objects.values() + ) + ) + assert fields + functions == [ + "exposed_field", + "exposed_method", + ] + + +def test_func_doc(): + mod = Module() + + @mod.object_type + class Foo: + @mod.function + def fn_with_doc(self): + """Foo.""" + + assert mod.get_object("Foo").functions["fn_with_doc"].doc == "Foo." + + +def test_function_deprecated_metadata(): + mod = Module() + + @mod.object_type + class Foo: + @mod.function(deprecated="Use new method instead") + def legacy(self): + """Legacy function.""" + + fn = mod.get_object("Foo").functions["legacy"] + assert fn.deprecated == "Use new method instead" + + +def test_function_check_metadata(): + mod = Module() + + @mod.object_type + class Foo: + @mod.function + @mod.check + def lint(self): + """Check function.""" + + fn = mod.get_object("Foo").functions["lint"] + assert fn.check is True + + +def test_function_check_default_false(): + mod = Module() + + @mod.object_type + class Foo: + @mod.function + def regular(self): + """Regular function.""" + + fn = mod.get_object("Foo").functions["regular"] + assert fn.check is False + + +def test_check_decorator_order(): + """Test that @check works whether applied before or after @function.""" + mod = Module() + + @mod.object_type + class Foo: + @mod.check + @mod.function + def check_first(self): + """Check applied before function.""" + + @mod.function + @mod.check + def function_first(self): + """Check applied after function.""" + + check_first_fn = mod.get_object("Foo").functions["check_first"] + assert check_first_fn.check is True + + function_first_fn = mod.get_object("Foo").functions["function_first"] + assert function_first_fn.check is True + + +def test_function_argument_deprecated_metadata(): + mod = Module() + + @mod.object_type + class Foo: + @mod.function + def legacy( + self, + value: Annotated[str, dagger.Deprecated("Use new argument instead")], + empty: Annotated[str, dagger.Deprecated()], + current: str, + ) -> str: + return value + + with pytest.raises( + BadUsageError, + match="Can't deprecate required parameter 'value'", + ): + _ = mod.get_object("Foo").functions["legacy"].parameters + + +def test_field_deprecated_metadata(): + mod = Module() + + @mod.object_type + class Foo: + legacy: str = mod.field(default="", deprecated="Use new field instead") + + field = mod.get_object("Foo").fields["legacy"] + assert field.meta.deprecated == "Use new field instead" + + +def test_object_type_deprecated_metadata(): + mod = Module() + + @mod.object_type(deprecated="Use NewFoo instead") + class Foo: + pass + + obj = mod.get_object("Foo") + assert obj.deprecated == "Use NewFoo instead" + + +def test_external_constructor_doc(): + mod = Module() + + @mod.object_type + class External: + """external docstring""" + + foo: Annotated[str, Doc("a foo walks into a bar")] = "bar" + + @mod.function + def bar(self) -> str: + return self.foo + + @mod.object_type + class Test: + external = mod.function()(External) + alternative = mod.function(doc="still external")(External) + + obj = mod.get_object("Test") + assert obj.functions["external"].doc == "external docstring" + assert obj.functions["alternative"].doc == "still external" + # all functions point to the same constructor, with the same arguments + for fn in obj.functions.values(): + for param in fn.parameters.values(): + assert param.name == "foo" + assert param.doc == "a foo walks into a bar" + assert param.default_value == dagger.JSON('"bar"') + + +def test_external_alt_constructor_doc(): + mod = Module() + + @mod.object_type + class External: + """An object""" + + @classmethod + def create(cls) -> "External": + """Factory constructor.""" + return cls() + + @mod.object_type + class Test: + external = mod.function()(External) + + assert mod.get_object("Test").functions["external"].doc == "Factory constructor." + + +def test_void_return_type(): + mod = Module() + + @mod.object_type + class Test: + @mod.function + def void(self): ... + + func = mod.get_object("Test").functions["void"] + assert func.return_type is None + assert to_typedef(func.return_type) == dag.type_def().with_optional(True).with_kind( + dagger.TypeDefKind.VOID_KIND + ) + + +@pytest.mark.anyio +async def test_self_return_type(): + mod = Module() + + @mod.object_type + class Test: + @mod.function + def iden(self) -> Self: + return self + + @mod.function + def seq(self) -> list[Self]: + return [self] + + obj = mod.get_object("Test") + iden = obj.functions["iden"] + seq = obj.functions["seq"] + assert iden.return_type is Test + assert seq.return_type == list[Test] + expected = dag.type_def().with_object("Test") + assert to_typedef(iden.return_type) == expected + assert to_typedef(seq.return_type) == dag.type_def().with_list_of(expected) diff --git a/sdk/tests/mod/test_results.py b/sdk/tests/mod/test_results.py new file mode 100644 index 0000000..b60b52c --- /dev/null +++ b/sdk/tests/mod/test_results.py @@ -0,0 +1,470 @@ +import enum +import typing +from dataclasses import InitVar +from typing import Annotated + +import pytest +import typing_extensions + +import dagger +from dagger import Doc, Name, dag +from dagger.mod import Module +from dagger.mod._exceptions import RegistrationError + +pytestmark = [ + pytest.mark.anyio, +] + + +@pytest.mark.slow +async def test_unstructure_structure(): + mod = Module() + + @mod.object_type + class Bar: + msg: Annotated[str, Doc("Echo message")] = mod.field(default="foobar") + ctr: Annotated[dagger.Container, Doc("A container")] = mod.field() + + @mod.function + async def bar(self) -> str: + return await self.ctr.with_exec(["echo", "-n", self.msg]).stdout() + + @mod.object_type + class Foo: + @mod.function + def foo(self) -> Bar: + return Bar(ctr=dag.container().from_("alpine")) + + async with dagger.connection(): + parent = await mod.get_result("Foo", {}, "foo", {}) + result = await mod.get_result("Bar", parent, "bar", {}) + + assert result == "foobar" + + +class TestNameOverrides: + @pytest.fixture(scope="class") + def mod(self): + _mod = Module() + + @_mod.object_type + class Bar: + with_: str = _mod.field() + with_x: str = _mod.field(name="withx") + + @_mod.object_type + class Foo: + from_: str = _mod.field(default="") + + @_mod.function + def bar(self) -> Bar: + return Bar(with_="bar", with_x="bax") + + @_mod.function + def import_(self, from_: str) -> str: + return from_ + + @_mod.function(name="importx") + def import_x(self, from_x: Annotated[str, Name("fromx")]) -> str: + return from_x + + return _mod + + async def test_function_and_arg_name_default(self, mod: Module): + parent = await mod.get_result("Foo", {}, "", {"from": "foo"}) + assert await mod.get_result("Foo", parent, "from", {}) == "foo" + assert await mod.get_result("Foo", {}, "import", {"from": "egg"}) == "egg" + + async def test_function_and_arg_name_custom(self, mod: Module): + assert await mod.get_result("Foo", {}, "importx", {"fromx": "egg"}) == "egg" + + async def test_field_unstructure(self, mod: Module): + assert await mod.get_result("Foo", {}, "bar", {}) == { + "with": "bar", + "withx": "bax", + } + + async def test_field_structure(self, mod: Module): + state = {"with": "baz", "withx": "bat"} + assert await mod.get_result("Bar", state, "with", {}) == "baz" + assert await mod.get_result("Bar", state, "withx", {}) == "bat" + + +async def test_method_returns_self(): + mod = Module() + + @mod.object_type + class Foo: + message: str = "foo" + + if hasattr(typing, "Self"): + + @mod.function + def foo(self) -> typing.Self: + self.message = "foobar" + return self + + @mod.function + def bar(self) -> typing_extensions.Self: + self.message = "barfoo" + return self + + if hasattr(typing, "Self"): + assert await mod.get_result("Foo", {}, "foo", {}) == {"message": "foobar"} + + assert await mod.get_result("Foo", {}, "bar", {}) == {"message": "barfoo"} + + +async def test_constructor_post_init(): + mod = Module() + + @mod.object_type + class Foo: + foo: str = mod.field(default="foo") + bar: str = mod.field(default="bar") + test: str = mod.field(init=False) + + def __post_init__(self): + self.test = self.foo + self.bar + + assert Foo().test == "foobar" + assert Foo(foo="oof", bar="rab").test == "oofrab" + assert await mod.get_result("Foo", {}, "test", {}) == "foobar" + assert await mod.get_result("Foo", {}, "", {}) == { + "foo": "foo", + "bar": "bar", + "test": "foobar", + } + + +async def test_overridden_init_constructor(): + mod = Module() + + @mod.object_type + class Foo: + # Default should be ignored due to __init__ override + # but still exposed as a field due to mod.field(). + foo: str = mod.field(default="foo") + + def __init__(self, bar: str = "bar", foo: str = "man"): + self.foo = bar + foo + + assert Foo().foo == "barman" + assert Foo(bar="bat").foo == "batman" + assert await mod.get_result("Foo", {}, "", {}) == {"foo": "barman"} + assert await mod.get_result("Foo", {}, "", {"bar": "bat"}) == {"foo": "batman"} + assert await mod.get_result("Foo", {}, "", {"foo": "stool"}) == {"foo": "barstool"} + + +async def test_alt_constructor(): + # __init__ is not really a constructor, it's a part of one. + # First, an instance is created via __new__ and then __init__ is called + # to initialize. This means that if a complex field is required, it needs + # to allow and default to a sentinel value (e.g., `str | None = None`) + # only to change it later. Depending on the use case, it might be better + # to use a factory method instead. Thus the alternative constructor, + # conventionally a classmethod named `create`. + mod = Module() + + @mod.object_type + class Foo: + foo: str = mod.field(default="oof") + + @classmethod + def create(cls, bar: str = "bar"): + return cls(foo=bar) + + assert Foo().foo == "oof" + assert Foo.create().foo == "bar" + assert await mod.get_result("Foo", {}, "", {}) == {"foo": "bar"} + assert await mod.get_result("Foo", {}, "", {"bar": "baz"}) == {"foo": "baz"} + + +async def test_constructor_doc(): + mod = Module() + + @mod.object_type + class Foo: + """Object doc.""" + + assert mod.get_object("Foo").get_constructor().doc == "Object doc." + + +async def test_alt_constructor_doc(): + mod = Module() + + @mod.object_type + class Foo: + """Object doc.""" + + @classmethod + def create(cls): + """Constructor doc.""" + return cls() + + assert mod.get_object("Foo").get_constructor().doc == "Constructor doc." + + +async def test_alt_async_constructor(): + # The alternative constructor also allows running async code. + mod = Module() + + async def default_value(): + return "bar" + + @mod.object_type + class Foo: + foo: str = mod.field() + + @classmethod + async def create(cls, bar: str | None = None): + if bar is None: + bar = await default_value() + return cls(foo=bar) + + # Default constructor is still available but argument is mandatory. + with pytest.raises(TypeError): + Foo() + + # However, the alternative constructor has a default async value. + assert (await Foo.create()).foo == "bar" + + # From the API, the alternative constructor should be used. + assert await mod.get_result("Foo", {}, "", {}) == {"foo": "bar"} + assert await mod.get_result("Foo", {}, "", {"bar": "baz"}) == {"foo": "baz"} + + +async def test_no_method_alt_constructor(): + # Should only be decorated as a classmethod. + mod = Module() + + @mod.object_type + class Foo: + foo: str = mod.field(default="oof") + + def create(self, bar: str): + return Foo(foo=f"{bar}!") + + assert await mod.get_result("Foo", {}, "", {}) == {"foo": "oof"} + assert await mod.get_result("Foo", {}, "", {"foo": "foo"}) == {"foo": "foo"} + + +async def test_no_staticmethod_alt_constructor(): + # Should only be decorated as a classmethod. + mod = Module() + + @mod.object_type + class Foo: + foo: str = mod.field(default="oof") + + @staticmethod + def create(bar: str): + return Foo(foo=f"{bar}!") + + assert await mod.get_result("Foo", {}, "", {}) == {"foo": "oof"} + assert await mod.get_result("Foo", {}, "", {"foo": "foo"}) == {"foo": "foo"} + + +async def test_non_constructor_create_function(): + mod = Module() + + @mod.object_type + class Foo: + foo: str = mod.field(default="foo") + + # Non classmethod can use the name `create`. + @mod.function + def create(self) -> str: + return f"{self.foo}bar" + + assert await mod.get_result("Foo", {}, "create", {}) == "foobar" + + +class TestFunctionFromExternalConstructor: + @pytest.fixture(scope="class") + def mod(self): + _mod = Module() + + @_mod.object_type + class Bar: + baz: int = _mod.field(default=144) + + @_mod.object_type + class Foo: + egg: str = _mod.field(default="chick") + + # TODO: Written as `function()(Bar)` to avoid Pyright false negative + # on dataclass field missing an annotation. Fix warning. + + # create a `Test.bar() -> Bar` constructor function + bas = _mod.function()(Bar) + + # create a `Test.bat() -> Bar` constructor function + bat = _mod.function(name="bat")(Bar) + + return _mod + + async def test_chain(self, mod: Module): + # Assert Foo.bar() -> Bar + chains = [ + ("Foo", "bas", {"baz": 144}), + ("Bar", "baz", 144), + ] + result = {} + for parent_name, function, expected in chains: + result = await mod.get_result(parent_name, result, function, {}) + assert result == expected + + async def test_name_overrides_and_inputs(self, mod: Module): + # Assert Foo.bat(baz=33) -> Bar + chains = [ + ("Foo", "bat", {"baz": 33}), + ("Bar", "baz", {}), + ] + result = {} + for parent_name, function, inputs in chains: + result = await mod.get_result(parent_name, result, function, inputs) + if inputs: + assert result == inputs + assert result == 33 + + @pytest.mark.parametrize("origin", ["Foo", "Bar"]) + async def test_resolver_with_multiple_origins(self, origin): + mod = Module() + + @mod.object_type + class Test: + egg: str = mod.field(default="white") + + @mod.object_type + class Foo: + foo: str = mod.field(default="foo") + test = mod.function()(Test) + + @mod.object_type + class Bar: + bar: str = mod.field(default="bar") + test = mod.function()(Test) + + assert await mod.get_result(origin, {}, "test", {}) == {"egg": "white"} + + +async def test_external_alt_constructor(): + mod = Module() + + @mod.object_type + class Bar: + bar: str = mod.field(default="bar") + + @classmethod + def create(cls, bat: str): + return cls(bar=bat) + + @mod.object_type + class Foo: + bar = mod.function()(Bar) + + assert await mod.get_result("Foo", {}, "bar", {"bat": "bat"}) == {"bar": "bat"} + + +async def test_constructor_with_init_var(): + mod = Module() + + @mod.object_type + class Foo: + foo: str = mod.field(default="foo") + bar: InitVar[str] = mod.field(default="bar") + + def __post_init__(self, bar: str): + self.foo = self.foo + bar + + assert await mod.get_result("Foo", {}, "foo", {}) == "foobar" + assert await mod.get_result("Foo", {}, "", {"foo": "rab", "bar": "oof"}) == { + "foo": "raboof", + } + with pytest.raises(RegistrationError): + await mod.get_result("Foo", {}, "bar", {}) + + +def test_exposed_field_not_in_constructor(): + mod = Module() + + @mod.object_type + class Foo: + bat: str + bar: str = mod.field(default="man", init=False) + + with pytest.raises(TypeError, match="unexpected keyword argument 'bar'"): + Foo(bat="man", bar="stool") + + +async def test_enum_conversion(): + mod = Module() + + @mod.enum_type + class Custom(enum.Enum): + ONE = "1" + TWO = "2" + + @mod.enum_type + class Primitive(str, enum.Enum): + THREE = "3" + FOUR = "4" + + with pytest.warns(DeprecationWarning, match="enum.Enum"): + + @mod.enum_type + class Compat(dagger.Enum): + FIVE = "5" + SIX = "6" + + @mod.object_type + class Test: + custom: Custom = mod.field(default=Custom.ONE) + + @mod.function + def unstruct_custom(self) -> Custom: + return Custom.ONE + + @mod.function + def struct_custom(self, val: Custom) -> str: + return str(val) + + @mod.function + def unstruct_primitive(self) -> Primitive: + return Primitive.THREE + + @mod.function + def struct_primitive(self, val: Primitive) -> str: + return str(val) + + @mod.function + def unstruct_compat(self) -> Compat: + return Compat.FIVE + + @mod.function + def struct_compat(self, val: Compat) -> str: + return repr(val) + + obj, _ = await mod.get_structured_result("Test", {}, "", {}) + assert obj.custom == Custom.ONE + + obj, _ = await mod.get_structured_result("Test", {}, "", {"custom": "TWO"}) + assert obj.custom == Custom.TWO + + assert await mod.get_result("Test", {}, "unstruct_custom", {}) == "ONE" + assert ( + await mod.get_result("Test", {}, "struct_custom", {"val": "TWO"}) + == "Custom.TWO" + ) + + assert await mod.get_result("Test", {}, "unstruct_primitive", {}) == "THREE" + assert ( + await mod.get_result("Test", {}, "struct_primitive", {"val": "FOUR"}) + == "Primitive.FOUR" + ) + + assert await mod.get_result("Test", {}, "unstruct_compat", {}) == "FIVE" + assert await mod.get_result("Test", {}, "struct_compat", {"val": "SIX"}) == repr( + Compat.SIX + ) diff --git a/sdk/tests/mod/test_utils.py b/sdk/tests/mod/test_utils.py new file mode 100644 index 0000000..51dd07d --- /dev/null +++ b/sdk/tests/mod/test_utils.py @@ -0,0 +1,185 @@ +import dataclasses +from typing import Annotated, List, Optional, Protocol # noqa: UP035 + +import pytest +from beartype.door import TypeHint +from typing_extensions import Doc, Self + +from dagger import Name +from dagger.mod._utils import ( + get_alt_name, + get_doc, + is_list_type, + is_nullable, + list_of, + non_null, + normalize_name, +) + + +@pytest.mark.parametrize( + ("typ", "expected"), + [ + (str, False), + (str | int, False), + (str | None, True), + (Optional[str], True), + ], +) +def test_is_nullable(typ, expected): + assert is_nullable(TypeHint(typ)) == expected + + +@pytest.mark.parametrize( + ("typ", "expected"), + [ + (str, str), + (str | None, str), + (Optional[str], str), + (str | int | None, str | int), + (str | int, str | int), + ], +) +def test_non_optional(typ, expected): + assert non_null(TypeHint(typ)) == TypeHint(expected) + + +@dataclasses.dataclass +class ObjWithDoc: + """Foo.""" + + @classmethod + def create(cls) -> Self: + """Bar.""" + return cls() + + def with_doc(self): + """Foo.""" + + async def async_with_doc(self): + """Foo.""" + + +class IfaceWithDoc(Protocol): + """Foo.""" + + def with_doc(self): + """Foo.""" + + async def async_with_doc(self): + """Foo.""" + + +@pytest.mark.parametrize( + "annotation", + [ + ObjWithDoc, + ObjWithDoc.with_doc, + ObjWithDoc.async_with_doc, + IfaceWithDoc, + IfaceWithDoc.with_doc, + IfaceWithDoc.async_with_doc, + Annotated[str, Doc("Foo.")], + Annotated[str | None, Doc("Foo.")], + Annotated[str, Doc("Foo."), "Not supported"], + Annotated[str, Doc("Bar."), Doc("Foo.")], + ], +) +def test_get_doc(annotation): + assert get_doc(annotation) == "Foo." + + +def test_get_factory_doc(): + assert get_doc(ObjWithDoc.create) == "Bar." + + +@dataclasses.dataclass +class ObjWithoutDoc: + def without_doc(self): ... + async def async_without_doc(self): ... + + +class IfaceWithoutDoc(Protocol): + def without_doc(self): ... + async def async_without_doc(self): ... + + +@pytest.mark.parametrize( + "annotation", + [ + ObjWithoutDoc, + ObjWithoutDoc.without_doc, + ObjWithoutDoc.async_without_doc, + IfaceWithoutDoc, + IfaceWithoutDoc.without_doc, + IfaceWithoutDoc.async_without_doc, + str, + str | None, + Annotated[str, "Not supported"], + Annotated[str, Name("foo")], + ], +) +def test_no_annotated_doc(annotation): + assert get_doc(annotation) is None + + +@pytest.mark.parametrize( + ("name", "expected"), + [ + ("with_", "with"), + ("__init__", "__init__"), + ("_private_", "_private_"), + ("mangled__", "mangled__"), + ], +) +def test_normalize_name(name: str, expected: str): + assert normalize_name(name) == expected + + +def test_get_alt_name(): + assert get_alt_name(Annotated[str, Name("foo")]) == "foo" + + +def test_get_last_alt_name(): + assert get_alt_name(Annotated[str, Name("foo"), Name("bar")]) == "bar" + + +@pytest.mark.parametrize( + "annotation", + [ + str, + Annotated[str, Doc("foo")], + ], +) +def test_no_get_alt_name(annotation): + assert get_alt_name(annotation) is None + + +@pytest.mark.parametrize( + ("typ", "expected"), + [ + (str, False), + (list[str], True), + (List[str], True), # noqa: UP006 + (tuple[str, int], False), + (tuple[str, ...], True), + ], +) +def test_is_list(typ, expected): + assert is_list_type(typ) == expected + + +class Foo: ... + + +@pytest.mark.parametrize( + ("typ", "expected"), + [ + (str, None), + (list[str], str), + (List[str], str), # noqa: UP006 + (list[Foo], Foo), + ], +) +def test_list_of(typ, expected): + assert list_of(typ) == expected diff --git a/sdk/uv.lock b/sdk/uv.lock new file mode 100644 index 0000000..eaa4f76 --- /dev/null +++ b/sdk/uv.lock @@ -0,0 +1,1958 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.12'", + "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.13.3" +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/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88", size = 7844556, upload-time = "2026-01-03T17:33:05.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/d6/5aec9313ee6ea9c7cde8b891b69f4ff4001416867104580670a31daeba5b/aiohttp-3.13.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5a372fd5afd301b3a89582817fdcdb6c34124787c70dbcc616f259013e7eef7", size = 738950, upload-time = "2026-01-03T17:29:13.002Z" }, + { url = "https://files.pythonhosted.org/packages/68/03/8fa90a7e6d11ff20a18837a8e2b5dd23db01aabc475aa9271c8ad33299f5/aiohttp-3.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:147e422fd1223005c22b4fe080f5d93ced44460f5f9c105406b753612b587821", size = 496099, upload-time = "2026-01-03T17:29:15.268Z" }, + { url = "https://files.pythonhosted.org/packages/d2/23/b81f744d402510a8366b74eb420fc0cc1170d0c43daca12d10814df85f10/aiohttp-3.13.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:859bd3f2156e81dd01432f5849fc73e2243d4a487c4fd26609b1299534ee1845", size = 491072, upload-time = "2026-01-03T17:29:16.922Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e1/56d1d1c0dd334cd203dd97706ce004c1aa24b34a813b0b8daf3383039706/aiohttp-3.13.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dca68018bf48c251ba17c72ed479f4dafe9dbd5a73707ad8d28a38d11f3d42af", size = 1671588, upload-time = "2026-01-03T17:29:18.539Z" }, + { url = "https://files.pythonhosted.org/packages/5f/34/8d7f962604f4bc2b4e39eb1220dac7d4e4cba91fb9ba0474b4ecd67db165/aiohttp-3.13.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fee0c6bc7db1de362252affec009707a17478a00ec69f797d23ca256e36d5940", size = 1640334, upload-time = "2026-01-03T17:29:21.028Z" }, + { url = "https://files.pythonhosted.org/packages/94/1d/fcccf2c668d87337ddeef9881537baee13c58d8f01f12ba8a24215f2b804/aiohttp-3.13.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c048058117fd649334d81b4b526e94bde3ccaddb20463a815ced6ecbb7d11160", size = 1722656, upload-time = "2026-01-03T17:29:22.531Z" }, + { url = "https://files.pythonhosted.org/packages/aa/98/c6f3b081c4c606bc1e5f2ec102e87d6411c73a9ef3616fea6f2d5c98c062/aiohttp-3.13.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:215a685b6fbbfcf71dfe96e3eba7a6f58f10da1dfdf4889c7dd856abe430dca7", size = 1817625, upload-time = "2026-01-03T17:29:24.276Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c0/cfcc3d2e11b477f86e1af2863f3858c8850d751ce8dc39c4058a072c9e54/aiohttp-3.13.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2c184bb1fe2cbd2cefba613e9db29a5ab559323f994b6737e370d3da0ac455", size = 1672604, upload-time = "2026-01-03T17:29:26.099Z" }, + { url = "https://files.pythonhosted.org/packages/1e/77/6b4ffcbcac4c6a5d041343a756f34a6dd26174ae07f977a64fe028dda5b0/aiohttp-3.13.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:75ca857eba4e20ce9f546cd59c7007b33906a4cd48f2ff6ccf1ccfc3b646f279", size = 1554370, upload-time = "2026-01-03T17:29:28.121Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f0/e3ddfa93f17d689dbe014ba048f18e0c9f9b456033b70e94349a2e9048be/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81e97251d9298386c2b7dbeb490d3d1badbdc69107fb8c9299dd04eb39bddc0e", size = 1642023, upload-time = "2026-01-03T17:29:30.002Z" }, + { url = "https://files.pythonhosted.org/packages/eb/45/c14019c9ec60a8e243d06d601b33dcc4fd92379424bde3021725859d7f99/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c0e2d366af265797506f0283487223146af57815b388623f0357ef7eac9b209d", size = 1649680, upload-time = "2026-01-03T17:29:31.782Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fd/09c9451dae5aa5c5ed756df95ff9ef549d45d4be663bafd1e4954fd836f0/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4e239d501f73d6db1522599e14b9b321a7e3b1de66ce33d53a765d975e9f4808", size = 1692407, upload-time = "2026-01-03T17:29:33.392Z" }, + { url = "https://files.pythonhosted.org/packages/a6/81/938bc2ec33c10efd6637ccb3d22f9f3160d08e8f3aa2587a2c2d5ab578eb/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0db318f7a6f065d84cb1e02662c526294450b314a02bd9e2a8e67f0d8564ce40", size = 1543047, upload-time = "2026-01-03T17:29:34.855Z" }, + { url = "https://files.pythonhosted.org/packages/f7/23/80488ee21c8d567c83045e412e1d9b7077d27171591a4eb7822586e8c06a/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:bfc1cc2fe31a6026a8a88e4ecfb98d7f6b1fec150cfd708adbfd1d2f42257c29", size = 1715264, upload-time = "2026-01-03T17:29:36.389Z" }, + { url = "https://files.pythonhosted.org/packages/e2/83/259a8da6683182768200b368120ab3deff5370bed93880fb9a3a86299f34/aiohttp-3.13.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af71fff7bac6bb7508956696dce8f6eec2bbb045eceb40343944b1ae62b5ef11", size = 1657275, upload-time = "2026-01-03T17:29:38.162Z" }, + { url = "https://files.pythonhosted.org/packages/3f/4f/2c41f800a0b560785c10fb316216ac058c105f9be50bdc6a285de88db625/aiohttp-3.13.3-cp310-cp310-win32.whl", hash = "sha256:37da61e244d1749798c151421602884db5270faf479cf0ef03af0ff68954c9dd", size = 434053, upload-time = "2026-01-03T17:29:40.074Z" }, + { url = "https://files.pythonhosted.org/packages/80/df/29cd63c7ecfdb65ccc12f7d808cac4fa2a19544660c06c61a4a48462de0c/aiohttp-3.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:7e63f210bc1b57ef699035f2b4b6d9ce096b5914414a49b0997c839b2bd2223c", size = 456687, upload-time = "2026-01-03T17:29:41.819Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4c/a164164834f03924d9a29dc3acd9e7ee58f95857e0b467f6d04298594ebb/aiohttp-3.13.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b6073099fb654e0a068ae678b10feff95c5cae95bbfcbfa7af669d361a8aa6b", size = 746051, upload-time = "2026-01-03T17:29:43.287Z" }, + { url = "https://files.pythonhosted.org/packages/82/71/d5c31390d18d4f58115037c432b7e0348c60f6f53b727cad33172144a112/aiohttp-3.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cb93e166e6c28716c8c6aeb5f99dfb6d5ccf482d29fe9bf9a794110e6d0ab64", size = 499234, upload-time = "2026-01-03T17:29:44.822Z" }, + { url = "https://files.pythonhosted.org/packages/0e/c9/741f8ac91e14b1d2e7100690425a5b2b919a87a5075406582991fb7de920/aiohttp-3.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28e027cf2f6b641693a09f631759b4d9ce9165099d2b5d92af9bd4e197690eea", size = 494979, upload-time = "2026-01-03T17:29:46.405Z" }, + { url = "https://files.pythonhosted.org/packages/75/b5/31d4d2e802dfd59f74ed47eba48869c1c21552c586d5e81a9d0d5c2ad640/aiohttp-3.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b61b7169ababd7802f9568ed96142616a9118dd2be0d1866e920e77ec8fa92a", size = 1748297, upload-time = "2026-01-03T17:29:48.083Z" }, + { url = "https://files.pythonhosted.org/packages/1a/3e/eefad0ad42959f226bb79664826883f2687d602a9ae2941a18e0484a74d3/aiohttp-3.13.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:80dd4c21b0f6237676449c6baaa1039abae86b91636b6c91a7f8e61c87f89540", size = 1707172, upload-time = "2026-01-03T17:29:49.648Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3a/54a64299fac2891c346cdcf2aa6803f994a2e4beeaf2e5a09dcc54acc842/aiohttp-3.13.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65d2ccb7eabee90ce0503c17716fc77226be026dcc3e65cce859a30db715025b", size = 1805405, upload-time = "2026-01-03T17:29:51.244Z" }, + { url = "https://files.pythonhosted.org/packages/6c/70/ddc1b7169cf64075e864f64595a14b147a895a868394a48f6a8031979038/aiohttp-3.13.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b179331a481cb5529fca8b432d8d3c7001cb217513c94cd72d668d1248688a3", size = 1899449, upload-time = "2026-01-03T17:29:53.938Z" }, + { url = "https://files.pythonhosted.org/packages/a1/7e/6815aab7d3a56610891c76ef79095677b8b5be6646aaf00f69b221765021/aiohttp-3.13.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d4c940f02f49483b18b079d1c27ab948721852b281f8b015c058100e9421dd1", size = 1748444, upload-time = "2026-01-03T17:29:55.484Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f2/073b145c4100da5511f457dc0f7558e99b2987cf72600d42b559db856fbc/aiohttp-3.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9444f105664c4ce47a2a7171a2418bce5b7bae45fb610f4e2c36045d85911d3", size = 1606038, upload-time = "2026-01-03T17:29:57.179Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c1/778d011920cae03ae01424ec202c513dc69243cf2db303965615b81deeea/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:694976222c711d1d00ba131904beb60534f93966562f64440d0c9d41b8cdb440", size = 1724156, upload-time = "2026-01-03T17:29:58.914Z" }, + { url = "https://files.pythonhosted.org/packages/0e/cb/3419eabf4ec1e9ec6f242c32b689248365a1cf621891f6f0386632525494/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f33ed1a2bf1997a36661874b017f5c4b760f41266341af36febaf271d179f6d7", size = 1722340, upload-time = "2026-01-03T17:30:01.962Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e5/76cf77bdbc435bf233c1f114edad39ed4177ccbfab7c329482b179cff4f4/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e636b3c5f61da31a92bf0d91da83e58fdfa96f178ba682f11d24f31944cdd28c", size = 1783041, upload-time = "2026-01-03T17:30:03.609Z" }, + { url = "https://files.pythonhosted.org/packages/9d/d4/dd1ca234c794fd29c057ce8c0566b8ef7fd6a51069de5f06fa84b9a1971c/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5d2d94f1f5fcbe40838ac51a6ab5704a6f9ea42e72ceda48de5e6b898521da51", size = 1596024, upload-time = "2026-01-03T17:30:05.132Z" }, + { url = "https://files.pythonhosted.org/packages/55/58/4345b5f26661a6180afa686c473620c30a66afdf120ed3dd545bbc809e85/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2be0e9ccf23e8a94f6f0650ce06042cefc6ac703d0d7ab6c7a917289f2539ad4", size = 1804590, upload-time = "2026-01-03T17:30:07.135Z" }, + { url = "https://files.pythonhosted.org/packages/7b/06/05950619af6c2df7e0a431d889ba2813c9f0129cec76f663e547a5ad56f2/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9af5e68ee47d6534d36791bbe9b646d2a7c7deb6fc24d7943628edfbb3581f29", size = 1740355, upload-time = "2026-01-03T17:30:09.083Z" }, + { url = "https://files.pythonhosted.org/packages/3e/80/958f16de79ba0422d7c1e284b2abd0c84bc03394fbe631d0a39ffa10e1eb/aiohttp-3.13.3-cp311-cp311-win32.whl", hash = "sha256:a2212ad43c0833a873d0fb3c63fa1bacedd4cf6af2fee62bf4b739ceec3ab239", size = 433701, upload-time = "2026-01-03T17:30:10.869Z" }, + { url = "https://files.pythonhosted.org/packages/dc/f2/27cdf04c9851712d6c1b99df6821a6623c3c9e55956d4b1e318c337b5a48/aiohttp-3.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:642f752c3eb117b105acbd87e2c143de710987e09860d674e068c4c2c441034f", size = 457678, upload-time = "2026-01-03T17:30:12.719Z" }, + { url = "https://files.pythonhosted.org/packages/a0/be/4fc11f202955a69e0db803a12a062b8379c970c7c84f4882b6da17337cc1/aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c", size = 739732, upload-time = "2026-01-03T17:30:14.23Z" }, + { url = "https://files.pythonhosted.org/packages/97/2c/621d5b851f94fa0bb7430d6089b3aa970a9d9b75196bc93bb624b0db237a/aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168", size = 494293, upload-time = "2026-01-03T17:30:15.96Z" }, + { url = "https://files.pythonhosted.org/packages/5d/43/4be01406b78e1be8320bb8316dc9c42dbab553d281c40364e0f862d5661c/aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d", size = 493533, upload-time = "2026-01-03T17:30:17.431Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a8/5a35dc56a06a2c90d4742cbf35294396907027f80eea696637945a106f25/aiohttp-3.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d32764c6c9aafb7fb55366a224756387cd50bfa720f32b88e0e6fa45b27dcf29", size = 1737839, upload-time = "2026-01-03T17:30:19.422Z" }, + { url = "https://files.pythonhosted.org/packages/bf/62/4b9eeb331da56530bf2e198a297e5303e1c1ebdceeb00fe9b568a65c5a0c/aiohttp-3.13.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1a6102b4d3ebc07dad44fbf07b45bb600300f15b552ddf1851b5390202ea2e3", size = 1703932, upload-time = "2026-01-03T17:30:21.756Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f6/af16887b5d419e6a367095994c0b1332d154f647e7dc2bd50e61876e8e3d/aiohttp-3.13.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c014c7ea7fb775dd015b2d3137378b7be0249a448a1612268b5a90c2d81de04d", size = 1771906, upload-time = "2026-01-03T17:30:23.932Z" }, + { url = "https://files.pythonhosted.org/packages/ce/83/397c634b1bcc24292fa1e0c7822800f9f6569e32934bdeef09dae7992dfb/aiohttp-3.13.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2b8d8ddba8f95ba17582226f80e2de99c7a7948e66490ef8d947e272a93e9463", size = 1871020, upload-time = "2026-01-03T17:30:26Z" }, + { url = "https://files.pythonhosted.org/packages/86/f6/a62cbbf13f0ac80a70f71b1672feba90fdb21fd7abd8dbf25c0105fb6fa3/aiohttp-3.13.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ae8dd55c8e6c4257eae3a20fd2c8f41edaea5992ed67156642493b8daf3cecc", size = 1755181, upload-time = "2026-01-03T17:30:27.554Z" }, + { url = "https://files.pythonhosted.org/packages/0a/87/20a35ad487efdd3fba93d5843efdfaa62d2f1479eaafa7453398a44faf13/aiohttp-3.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01ad2529d4b5035578f5081606a465f3b814c542882804e2e8cda61adf5c71bf", size = 1561794, upload-time = "2026-01-03T17:30:29.254Z" }, + { url = "https://files.pythonhosted.org/packages/de/95/8fd69a66682012f6716e1bc09ef8a1a2a91922c5725cb904689f112309c4/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bb4f7475e359992b580559e008c598091c45b5088f28614e855e42d39c2f1033", size = 1697900, upload-time = "2026-01-03T17:30:31.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/66/7b94b3b5ba70e955ff597672dad1691333080e37f50280178967aff68657/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c19b90316ad3b24c69cd78d5c9b4f3aa4497643685901185b65166293d36a00f", size = 1728239, upload-time = "2026-01-03T17:30:32.703Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/6f72f77f9f7d74719692ab65a2a0252584bf8d5f301e2ecb4c0da734530a/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:96d604498a7c782cb15a51c406acaea70d8c027ee6b90c569baa6e7b93073679", size = 1740527, upload-time = "2026-01-03T17:30:34.695Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b4/75ec16cbbd5c01bdaf4a05b19e103e78d7ce1ef7c80867eb0ace42ff4488/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:084911a532763e9d3dd95adf78a78f4096cd5f58cdc18e6fdbc1b58417a45423", size = 1554489, upload-time = "2026-01-03T17:30:36.864Z" }, + { url = "https://files.pythonhosted.org/packages/52/8f/bc518c0eea29f8406dcf7ed1f96c9b48e3bc3995a96159b3fc11f9e08321/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7a4a94eb787e606d0a09404b9c38c113d3b099d508021faa615d70a0131907ce", size = 1767852, upload-time = "2026-01-03T17:30:39.433Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f2/a07a75173124f31f11ea6f863dc44e6f09afe2bca45dd4e64979490deab1/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87797e645d9d8e222e04160ee32aa06bc5c163e8499f24db719e7852ec23093a", size = 1722379, upload-time = "2026-01-03T17:30:41.081Z" }, + { url = "https://files.pythonhosted.org/packages/3c/4a/1a3fee7c21350cac78e5c5cef711bac1b94feca07399f3d406972e2d8fcd/aiohttp-3.13.3-cp312-cp312-win32.whl", hash = "sha256:b04be762396457bef43f3597c991e192ee7da460a4953d7e647ee4b1c28e7046", size = 428253, upload-time = "2026-01-03T17:30:42.644Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b7/76175c7cb4eb73d91ad63c34e29fc4f77c9386bba4a65b53ba8e05ee3c39/aiohttp-3.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:e3531d63d3bdfa7e3ac5e9b27b2dd7ec9df3206a98e0b3445fa906f233264c57", size = 455407, upload-time = "2026-01-03T17:30:44.195Z" }, + { url = "https://files.pythonhosted.org/packages/97/8a/12ca489246ca1faaf5432844adbfce7ff2cc4997733e0af120869345643a/aiohttp-3.13.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5dff64413671b0d3e7d5918ea490bdccb97a4ad29b3f311ed423200b2203e01c", size = 734190, upload-time = "2026-01-03T17:30:45.832Z" }, + { url = "https://files.pythonhosted.org/packages/32/08/de43984c74ed1fca5c014808963cc83cb00d7bb06af228f132d33862ca76/aiohttp-3.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:87b9aab6d6ed88235aa2970294f496ff1a1f9adcd724d800e9b952395a80ffd9", size = 491783, upload-time = "2026-01-03T17:30:47.466Z" }, + { url = "https://files.pythonhosted.org/packages/17/f8/8dd2cf6112a5a76f81f81a5130c57ca829d101ad583ce57f889179accdda/aiohttp-3.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:425c126c0dc43861e22cb1c14ba4c8e45d09516d0a3ae0a3f7494b79f5f233a3", size = 490704, upload-time = "2026-01-03T17:30:49.373Z" }, + { url = "https://files.pythonhosted.org/packages/6d/40/a46b03ca03936f832bc7eaa47cfbb1ad012ba1be4790122ee4f4f8cba074/aiohttp-3.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f9120f7093c2a32d9647abcaf21e6ad275b4fbec5b55969f978b1a97c7c86bf", size = 1720652, upload-time = "2026-01-03T17:30:50.974Z" }, + { url = "https://files.pythonhosted.org/packages/f7/7e/917fe18e3607af92657e4285498f500dca797ff8c918bd7d90b05abf6c2a/aiohttp-3.13.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:697753042d57f4bf7122cab985bf15d0cef23c770864580f5af4f52023a56bd6", size = 1692014, upload-time = "2026-01-03T17:30:52.729Z" }, + { url = "https://files.pythonhosted.org/packages/71/b6/cefa4cbc00d315d68973b671cf105b21a609c12b82d52e5d0c9ae61d2a09/aiohttp-3.13.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6de499a1a44e7de70735d0b39f67c8f25eb3d91eb3103be99ca0fa882cdd987d", size = 1759777, upload-time = "2026-01-03T17:30:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e3/e06ee07b45e59e6d81498b591fc589629be1553abb2a82ce33efe2a7b068/aiohttp-3.13.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:37239e9f9a7ea9ac5bf6b92b0260b01f8a22281996da609206a84df860bc1261", size = 1861276, upload-time = "2026-01-03T17:30:56.512Z" }, + { url = "https://files.pythonhosted.org/packages/7c/24/75d274228acf35ceeb2850b8ce04de9dd7355ff7a0b49d607ee60c29c518/aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f76c1e3fe7d7c8afad7ed193f89a292e1999608170dcc9751a7462a87dfd5bc0", size = 1743131, upload-time = "2026-01-03T17:30:58.256Z" }, + { url = "https://files.pythonhosted.org/packages/04/98/3d21dde21889b17ca2eea54fdcff21b27b93f45b7bb94ca029c31ab59dc3/aiohttp-3.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fc290605db2a917f6e81b0e1e0796469871f5af381ce15c604a3c5c7e51cb730", size = 1556863, upload-time = "2026-01-03T17:31:00.445Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/da0c3ab1192eaf64782b03971ab4055b475d0db07b17eff925e8c93b3aa5/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4021b51936308aeea0367b8f006dc999ca02bc118a0cc78c303f50a2ff6afb91", size = 1682793, upload-time = "2026-01-03T17:31:03.024Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0f/5802ada182f575afa02cbd0ec5180d7e13a402afb7c2c03a9aa5e5d49060/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:49a03727c1bba9a97d3e93c9f93ca03a57300f484b6e935463099841261195d3", size = 1716676, upload-time = "2026-01-03T17:31:04.842Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8c/714d53bd8b5a4560667f7bbbb06b20c2382f9c7847d198370ec6526af39c/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3d9908a48eb7416dc1f4524e69f1d32e5d90e3981e4e37eb0aa1cd18f9cfa2a4", size = 1733217, upload-time = "2026-01-03T17:31:06.868Z" }, + { url = "https://files.pythonhosted.org/packages/7d/79/e2176f46d2e963facea939f5be2d26368ce543622be6f00a12844d3c991f/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2712039939ec963c237286113c68dbad80a82a4281543f3abf766d9d73228998", size = 1552303, upload-time = "2026-01-03T17:31:08.958Z" }, + { url = "https://files.pythonhosted.org/packages/ab/6a/28ed4dea1759916090587d1fe57087b03e6c784a642b85ef48217b0277ae/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7bfdc049127717581866fa4708791220970ce291c23e28ccf3922c700740fdc0", size = 1763673, upload-time = "2026-01-03T17:31:10.676Z" }, + { url = "https://files.pythonhosted.org/packages/e8/35/4a3daeb8b9fab49240d21c04d50732313295e4bd813a465d840236dd0ce1/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8057c98e0c8472d8846b9c79f56766bcc57e3e8ac7bfd510482332366c56c591", size = 1721120, upload-time = "2026-01-03T17:31:12.575Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9f/d643bb3c5fb99547323e635e251c609fbbc660d983144cfebec529e09264/aiohttp-3.13.3-cp313-cp313-win32.whl", hash = "sha256:1449ceddcdbcf2e0446957863af03ebaaa03f94c090f945411b61269e2cb5daf", size = 427383, upload-time = "2026-01-03T17:31:14.382Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f1/ab0395f8a79933577cdd996dd2f9aa6014af9535f65dddcf88204682fe62/aiohttp-3.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:693781c45a4033d31d4187d2436f5ac701e7bbfe5df40d917736108c1cc7436e", size = 453899, upload-time = "2026-01-03T17:31:15.958Z" }, + { url = "https://files.pythonhosted.org/packages/99/36/5b6514a9f5d66f4e2597e40dea2e3db271e023eb7a5d22defe96ba560996/aiohttp-3.13.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:ea37047c6b367fd4bd632bff8077449b8fa034b69e812a18e0132a00fae6e808", size = 737238, upload-time = "2026-01-03T17:31:17.909Z" }, + { url = "https://files.pythonhosted.org/packages/f7/49/459327f0d5bcd8c6c9ca69e60fdeebc3622861e696490d8674a6d0cb90a6/aiohttp-3.13.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6fc0e2337d1a4c3e6acafda6a78a39d4c14caea625124817420abceed36e2415", size = 492292, upload-time = "2026-01-03T17:31:19.919Z" }, + { url = "https://files.pythonhosted.org/packages/e8/0b/b97660c5fd05d3495b4eb27f2d0ef18dc1dc4eff7511a9bf371397ff0264/aiohttp-3.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c685f2d80bb67ca8c3837823ad76196b3694b0159d232206d1e461d3d434666f", size = 493021, upload-time = "2026-01-03T17:31:21.636Z" }, + { url = "https://files.pythonhosted.org/packages/54/d4/438efabdf74e30aeceb890c3290bbaa449780583b1270b00661126b8aae4/aiohttp-3.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e377758516d262bde50c2584fc6c578af272559c409eecbdd2bae1601184d6", size = 1717263, upload-time = "2026-01-03T17:31:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/71/f2/7bddc7fd612367d1459c5bcf598a9e8f7092d6580d98de0e057eb42697ad/aiohttp-3.13.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:34749271508078b261c4abb1767d42b8d0c0cc9449c73a4df494777dc55f0687", size = 1669107, upload-time = "2026-01-03T17:31:25.334Z" }, + { url = "https://files.pythonhosted.org/packages/00/5a/1aeaecca40e22560f97610a329e0e5efef5e0b5afdf9f857f0d93839ab2e/aiohttp-3.13.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82611aeec80eb144416956ec85b6ca45a64d76429c1ed46ae1b5f86c6e0c9a26", size = 1760196, upload-time = "2026-01-03T17:31:27.394Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f8/0ff6992bea7bd560fc510ea1c815f87eedd745fe035589c71ce05612a19a/aiohttp-3.13.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2fff83cfc93f18f215896e3a190e8e5cb413ce01553901aca925176e7568963a", size = 1843591, upload-time = "2026-01-03T17:31:29.238Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d1/e30e537a15f53485b61f5be525f2157da719819e8377298502aebac45536/aiohttp-3.13.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bbe7d4cecacb439e2e2a8a1a7b935c25b812af7a5fd26503a66dadf428e79ec1", size = 1720277, upload-time = "2026-01-03T17:31:31.053Z" }, + { url = "https://files.pythonhosted.org/packages/84/45/23f4c451d8192f553d38d838831ebbc156907ea6e05557f39563101b7717/aiohttp-3.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b928f30fe49574253644b1ca44b1b8adbd903aa0da4b9054a6c20fc7f4092a25", size = 1548575, upload-time = "2026-01-03T17:31:32.87Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ed/0a42b127a43712eda7807e7892c083eadfaf8429ca8fb619662a530a3aab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7b5e8fe4de30df199155baaf64f2fcd604f4c678ed20910db8e2c66dc4b11603", size = 1679455, upload-time = "2026-01-03T17:31:34.76Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b5/c05f0c2b4b4fe2c9d55e73b6d3ed4fd6c9dc2684b1d81cbdf77e7fad9adb/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:8542f41a62bcc58fc7f11cf7c90e0ec324ce44950003feb70640fc2a9092c32a", size = 1687417, upload-time = "2026-01-03T17:31:36.699Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6b/915bc5dad66aef602b9e459b5a973529304d4e89ca86999d9d75d80cbd0b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5e1d8c8b8f1d91cd08d8f4a3c2b067bfca6ec043d3ff36de0f3a715feeedf926", size = 1729968, upload-time = "2026-01-03T17:31:38.622Z" }, + { url = "https://files.pythonhosted.org/packages/11/3b/e84581290a9520024a08640b63d07673057aec5ca548177a82026187ba73/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:90455115e5da1c3c51ab619ac57f877da8fd6d73c05aacd125c5ae9819582aba", size = 1545690, upload-time = "2026-01-03T17:31:40.57Z" }, + { url = "https://files.pythonhosted.org/packages/f5/04/0c3655a566c43fd647c81b895dfe361b9f9ad6d58c19309d45cff52d6c3b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:042e9e0bcb5fba81886c8b4fbb9a09d6b8a00245fd8d88e4d989c1f96c74164c", size = 1746390, upload-time = "2026-01-03T17:31:42.857Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/71165b26978f719c3419381514c9690bd5980e764a09440a10bb816ea4ab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2eb752b102b12a76ca02dff751a801f028b4ffbbc478840b473597fc91a9ed43", size = 1702188, upload-time = "2026-01-03T17:31:44.984Z" }, + { url = "https://files.pythonhosted.org/packages/29/a7/cbe6c9e8e136314fa1980da388a59d2f35f35395948a08b6747baebb6aa6/aiohttp-3.13.3-cp314-cp314-win32.whl", hash = "sha256:b556c85915d8efaed322bf1bdae9486aa0f3f764195a0fb6ee962e5c71ef5ce1", size = 433126, upload-time = "2026-01-03T17:31:47.463Z" }, + { url = "https://files.pythonhosted.org/packages/de/56/982704adea7d3b16614fc5936014e9af85c0e34b58f9046655817f04306e/aiohttp-3.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9bf9f7a65e7aa20dd764151fb3d616c81088f91f8df39c3893a536e279b4b984", size = 459128, upload-time = "2026-01-03T17:31:49.2Z" }, + { url = "https://files.pythonhosted.org/packages/6c/2a/3c79b638a9c3d4658d345339d22070241ea341ed4e07b5ac60fb0f418003/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:05861afbbec40650d8a07ea324367cb93e9e8cc7762e04dd4405df99fa65159c", size = 769512, upload-time = "2026-01-03T17:31:51.134Z" }, + { url = "https://files.pythonhosted.org/packages/29/b9/3e5014d46c0ab0db8707e0ac2711ed28c4da0218c358a4e7c17bae0d8722/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2fc82186fadc4a8316768d61f3722c230e2c1dcab4200d52d2ebdf2482e47592", size = 506444, upload-time = "2026-01-03T17:31:52.85Z" }, + { url = "https://files.pythonhosted.org/packages/90/03/c1d4ef9a054e151cd7839cdc497f2638f00b93cbe8043983986630d7a80c/aiohttp-3.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0add0900ff220d1d5c5ebbf99ed88b0c1bbf87aa7e4262300ed1376a6b13414f", size = 510798, upload-time = "2026-01-03T17:31:54.91Z" }, + { url = "https://files.pythonhosted.org/packages/ea/76/8c1e5abbfe8e127c893fe7ead569148a4d5a799f7cf958d8c09f3eedf097/aiohttp-3.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:568f416a4072fbfae453dcf9a99194bbb8bdeab718e08ee13dfa2ba0e4bebf29", size = 1868835, upload-time = "2026-01-03T17:31:56.733Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ac/984c5a6f74c363b01ff97adc96a3976d9c98940b8969a1881575b279ac5d/aiohttp-3.13.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:add1da70de90a2569c5e15249ff76a631ccacfe198375eead4aadf3b8dc849dc", size = 1720486, upload-time = "2026-01-03T17:31:58.65Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9a/b7039c5f099c4eb632138728828b33428585031a1e658d693d41d07d89d1/aiohttp-3.13.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b47b7ba335d2e9b1239fa571131a87e2d8ec96b333e68b2a305e7a98b0bae2", size = 1847951, upload-time = "2026-01-03T17:32:00.989Z" }, + { url = "https://files.pythonhosted.org/packages/3c/02/3bec2b9a1ba3c19ff89a43a19324202b8eb187ca1e928d8bdac9bbdddebd/aiohttp-3.13.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dd4dce1c718e38081c8f35f323209d4c1df7d4db4bab1b5c88a6b4d12b74587", size = 1941001, upload-time = "2026-01-03T17:32:03.122Z" }, + { url = "https://files.pythonhosted.org/packages/37/df/d879401cedeef27ac4717f6426c8c36c3091c6e9f08a9178cc87549c537f/aiohttp-3.13.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34bac00a67a812570d4a460447e1e9e06fae622946955f939051e7cc895cfab8", size = 1797246, upload-time = "2026-01-03T17:32:05.255Z" }, + { url = "https://files.pythonhosted.org/packages/8d/15/be122de1f67e6953add23335c8ece6d314ab67c8bebb3f181063010795a7/aiohttp-3.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a19884d2ee70b06d9204b2727a7b9f983d0c684c650254679e716b0b77920632", size = 1627131, upload-time = "2026-01-03T17:32:07.607Z" }, + { url = "https://files.pythonhosted.org/packages/12/12/70eedcac9134cfa3219ab7af31ea56bc877395b1ac30d65b1bc4b27d0438/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ca7f2bb6ba8348a3614c7918cc4bb73268c5ac2a207576b7afea19d3d9f64", size = 1795196, upload-time = "2026-01-03T17:32:09.59Z" }, + { url = "https://files.pythonhosted.org/packages/32/11/b30e1b1cd1f3054af86ebe60df96989c6a414dd87e27ad16950eee420bea/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b0d95340658b9d2f11d9697f59b3814a9d3bb4b7a7c20b131df4bcef464037c0", size = 1782841, upload-time = "2026-01-03T17:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/88/0d/d98a9367b38912384a17e287850f5695c528cff0f14f791ce8ee2e4f7796/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1e53262fd202e4b40b70c3aff944a8155059beedc8a89bba9dc1f9ef06a1b56", size = 1795193, upload-time = "2026-01-03T17:32:13.705Z" }, + { url = "https://files.pythonhosted.org/packages/43/a5/a2dfd1f5ff5581632c7f6a30e1744deda03808974f94f6534241ef60c751/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d60ac9663f44168038586cab2157e122e46bdef09e9368b37f2d82d354c23f72", size = 1621979, upload-time = "2026-01-03T17:32:15.965Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f0/12973c382ae7c1cccbc4417e129c5bf54c374dfb85af70893646e1f0e749/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:90751b8eed69435bac9ff4e3d2f6b3af1f57e37ecb0fbeee59c0174c9e2d41df", size = 1822193, upload-time = "2026-01-03T17:32:18.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/5f/24155e30ba7f8c96918af1350eb0663e2430aad9e001c0489d89cd708ab1/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fc353029f176fd2b3ec6cfc71be166aba1936fe5d73dd1992ce289ca6647a9aa", size = 1769801, upload-time = "2026-01-03T17:32:20.25Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f8/7314031ff5c10e6ece114da79b338ec17eeff3a079e53151f7e9f43c4723/aiohttp-3.13.3-cp314-cp314t-win32.whl", hash = "sha256:2e41b18a58da1e474a057b3d35248d8320029f61d70a37629535b16a0c8f3767", size = 466523, upload-time = "2026-01-03T17:32:22.215Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/278a98c715ae467624eafe375542d8ba9b4383a016df8fdefe0ae28382a7/aiohttp-3.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:44531a36aa2264a1860089ffd4dce7baf875ee5a6079d5fb42e261c704ef7344", size = 499694, upload-time = "2026-01-03T17:32:24.546Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "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.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, +] + +[[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.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + +[[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.22.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/94/1009e248bbfbab11397abca7193bea6626806be9a327d399810d523a07cb/beartype-0.22.9.tar.gz", hash = "sha256:8f82b54aa723a2848a56008d18875f91c1db02c32ef6a62319a002e3e25a975f", size = 1608866, upload-time = "2025-12-13T06:50:30.72Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl", hash = "sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2", size = 1333658, upload-time = "2025-12-13T06:50:28.266Z" }, +] + +[[package]] +name = "cattrs" +version = "25.3.0" +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/6e/00/2432bb2d445b39b5407f0a90e01b9a271475eea7caf913d7a86bcb956385/cattrs-25.3.0.tar.gz", hash = "sha256:1ac88d9e5eda10436c4517e390a4142d88638fe682c436c93db7ce4a277b884a", size = 509321, upload-time = "2025-10-07T12:26:08.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/2b/a40e1488fdfa02d3f9a653a61a5935ea08b3c2225ee818db6a76c7ba9695/cattrs-25.3.0-py3-none-any.whl", hash = "sha256:9896e84e0a5bf723bc7b4b68f4481785367ce07a8a02e7e9ee6eb2819bc306ff", size = 70738, upload-time = "2025-10-07T12:26:06.603Z" }, +] + +[[package]] +name = "certifi" +version = "2026.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, + { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, + { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, + { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, + { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, + { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, + { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, + { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, + { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, + { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, + { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[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" +version = "0.0.0" +source = { editable = "." } +dependencies = [ + { name = "anyio" }, + { name = "beartype" }, + { name = "cattrs" }, + { name = "exceptiongroup" }, + { name = "gql", extra = ["httpx"] }, + { name = "httpcore" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-instrumentation-logging" }, + { name = "opentelemetry-sdk" }, + { name = "platformdirs" }, + { name = "rich" }, + { name = "typing-extensions" }, + { name = "yarl" }, +] + +[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 = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "sphinx-rtd-theme" }, +] + +[package.metadata] +requires-dist = [ + { name = "anyio", specifier = ">=3.6.2" }, + { name = "beartype", specifier = ">=0.22.0" }, + { name = "cattrs", specifier = ">=25.1.0" }, + { name = "exceptiongroup", specifier = ">=1.3.0" }, + { name = "gql", extras = ["httpx"], specifier = ">=4.0" }, + { name = "httpcore", specifier = ">=1.0.8" }, + { name = "opentelemetry-exporter-otlp-proto-http", specifier = ">=1.23.0" }, + { name = "opentelemetry-instrumentation-logging", specifier = ">=0.54b1" }, + { 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" }, + { name = "yarl", specifier = "!=1.24.1" }, +] + +[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" } +resolution-markers = [ + "python_full_version < '3.11'", +] +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 = "docutils" +version = "0.22.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/4a/557715d5047da48d54e659203b9335be7bfaafda2c3f627b7c47e0b3aaf3/frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011", size = 86230, upload-time = "2025-10-06T05:35:23.699Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fb/c85f9fed3ea8fe8740e5b46a59cc141c23b842eca617da8876cfce5f760e/frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565", size = 49621, upload-time = "2025-10-06T05:35:25.341Z" }, + { url = "https://files.pythonhosted.org/packages/63/70/26ca3f06aace16f2352796b08704338d74b6d1a24ca38f2771afbb7ed915/frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad", size = 49889, upload-time = "2025-10-06T05:35:26.797Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ed/c7895fd2fde7f3ee70d248175f9b6cdf792fb741ab92dc59cd9ef3bd241b/frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2", size = 219464, upload-time = "2025-10-06T05:35:28.254Z" }, + { url = "https://files.pythonhosted.org/packages/6b/83/4d587dccbfca74cb8b810472392ad62bfa100bf8108c7223eb4c4fa2f7b3/frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186", size = 221649, upload-time = "2025-10-06T05:35:29.454Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c6/fd3b9cd046ec5fff9dab66831083bc2077006a874a2d3d9247dea93ddf7e/frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e", size = 219188, upload-time = "2025-10-06T05:35:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ce/80/6693f55eb2e085fc8afb28cf611448fb5b90e98e068fa1d1b8d8e66e5c7d/frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450", size = 231748, upload-time = "2025-10-06T05:35:32.101Z" }, + { url = "https://files.pythonhosted.org/packages/97/d6/e9459f7c5183854abd989ba384fe0cc1a0fb795a83c033f0571ec5933ca4/frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef", size = 236351, upload-time = "2025-10-06T05:35:33.834Z" }, + { url = "https://files.pythonhosted.org/packages/97/92/24e97474b65c0262e9ecd076e826bfd1d3074adcc165a256e42e7b8a7249/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4", size = 218767, upload-time = "2025-10-06T05:35:35.205Z" }, + { url = "https://files.pythonhosted.org/packages/ee/bf/dc394a097508f15abff383c5108cb8ad880d1f64a725ed3b90d5c2fbf0bb/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff", size = 235887, upload-time = "2025-10-06T05:35:36.354Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/25b201b9c015dbc999a5baf475a257010471a1fa8c200c843fd4abbee725/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c", size = 228785, upload-time = "2025-10-06T05:35:37.949Z" }, + { url = "https://files.pythonhosted.org/packages/84/f4/b5bc148df03082f05d2dd30c089e269acdbe251ac9a9cf4e727b2dbb8a3d/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f", size = 230312, upload-time = "2025-10-06T05:35:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/db/4b/87e95b5d15097c302430e647136b7d7ab2398a702390cf4c8601975709e7/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7", size = 217650, upload-time = "2025-10-06T05:35:40.377Z" }, + { url = "https://files.pythonhosted.org/packages/e5/70/78a0315d1fea97120591a83e0acd644da638c872f142fd72a6cebee825f3/frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a", size = 39659, upload-time = "2025-10-06T05:35:41.863Z" }, + { url = "https://files.pythonhosted.org/packages/66/aa/3f04523fb189a00e147e60c5b2205126118f216b0aa908035c45336e27e4/frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6", size = 43837, upload-time = "2025-10-06T05:35:43.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/75/1135feecdd7c336938bd55b4dc3b0dfc46d85b9be12ef2628574b28de776/frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e", size = 39989, upload-time = "2025-10-06T05:35:44.596Z" }, + { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.72.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e5/7b/adfd75544c415c487b33061fe7ae526165241c1ea133f9a9125a56b39fd8/googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5", size = 147433, upload-time = "2025-11-06T18:29:24.087Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/ab/09169d5a4612a5f92490806649ac8d41e3ec9129c636754575b3553f4ea4/googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038", size = 297515, upload-time = "2025-11-06T18:29:13.14Z" }, +] + +[[package]] +name = "gql" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "backoff" }, + { name = "graphql-core" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/9f/cf224a88ed71eb223b7aa0b9ff0aa10d7ecc9a4acdca2279eb046c26d5dc/gql-4.0.0.tar.gz", hash = "sha256:f22980844eb6a7c0266ffc70f111b9c7e7c7c13da38c3b439afc7eab3d7c9c8e", size = 215644, upload-time = "2025-08-17T14:32:35.397Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/94/30bbd09e8d45339fa77a48f5778d74d47e9242c11b3cd1093b3d994770a5/gql-4.0.0-py3-none-any.whl", hash = "sha256:f3beed7c531218eb24d97cb7df031b4a84fdb462f4a2beb86e2633d395937479", size = 89900, upload-time = "2025-08-17T14:32:34.029Z" }, +] + +[package.optional-dependencies] +httpx = [ + { name = "httpx" }, +] + +[[package]] +name = "graphql-core" +version = "3.2.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/9b/037a640a2983b09aed4a823f9cf1729e6d780b0671f854efa4727a7affbe/graphql_core-3.2.7.tar.gz", hash = "sha256:27b6904bdd3b43f2a0556dad5d579bdfdeab1f38e8e8788e555bdcb586a6f62c", size = 513484, upload-time = "2025-11-01T22:30:40.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/14/933037032608787fb92e365883ad6a741c235e0ff992865ec5d904a38f1e/graphql_core-3.2.7-py3-none-any.whl", hash = "sha256:17fc8f3ca4a42913d8e24d9ac9f08deddf0a0b2483076575757f6c412ead2ec0", size = 207262, upload-time = "2025-11-01T22:30:38.912Z" }, +] + +[[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.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[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.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "librt" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/3f/4ca7dd7819bf8ff303aca39c3c60e5320e46e766ab7f7dd627d3b9c11bdf/librt-0.8.0.tar.gz", hash = "sha256:cb74cdcbc0103fc988e04e5c58b0b31e8e5dd2babb9182b6f9490488eb36324b", size = 177306, upload-time = "2026-02-12T14:53:54.743Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/e9/018cfd60629e0404e6917943789800aa2231defbea540a17b90cc4547b97/librt-0.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db63cf3586a24241e89ca1ce0b56baaec9d371a328bd186c529b27c914c9a1ef", size = 65690, upload-time = "2026-02-12T14:51:57.761Z" }, + { url = "https://files.pythonhosted.org/packages/b5/80/8d39980860e4d1c9497ee50e5cd7c4766d8cfd90d105578eae418e8ffcbc/librt-0.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ba9d9e60651615bc614be5e21a82cdb7b1769a029369cf4b4d861e4f19686fb6", size = 68373, upload-time = "2026-02-12T14:51:59.013Z" }, + { url = "https://files.pythonhosted.org/packages/2d/76/6e6f7a443af63977e421bd542551fec4072d9eaba02e671b05b238fe73bc/librt-0.8.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb4b3ad543084ed79f186741470b251b9d269cd8b03556f15a8d1a99a64b7de5", size = 197091, upload-time = "2026-02-12T14:52:00.642Z" }, + { url = "https://files.pythonhosted.org/packages/14/40/fa064181c231334c9f4cb69eb338132d39510c8928e84beba34b861d0a71/librt-0.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d2720335020219197380ccfa5c895f079ac364b4c429e96952cd6509934d8eb", size = 207350, upload-time = "2026-02-12T14:52:02.32Z" }, + { url = "https://files.pythonhosted.org/packages/50/49/e7f8438dd226305e3e5955d495114ad01448e6a6ffc0303289b4153b5fc5/librt-0.8.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9726305d3e53419d27fc8cdfcd3f9571f0ceae22fa6b5ea1b3662c2e538f833e", size = 219962, upload-time = "2026-02-12T14:52:03.884Z" }, + { url = "https://files.pythonhosted.org/packages/1f/2c/74086fc5d52e77107a3cc80a9a3209be6ad1c9b6bc99969d8d9bbf9fdfe4/librt-0.8.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc3d107f603b5ee7a79b6aa6f166551b99b32fb4a5303c4dfcb4222fc6a0335e", size = 212939, upload-time = "2026-02-12T14:52:05.537Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ae/d6917c0ebec9bc2e0293903d6a5ccc7cdb64c228e529e96520b277318f25/librt-0.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:41064a0c07b4cc7a81355ccc305cb097d6027002209ffca51306e65ee8293630", size = 221393, upload-time = "2026-02-12T14:52:07.164Z" }, + { url = "https://files.pythonhosted.org/packages/04/97/15df8270f524ce09ad5c19cbbe0e8f95067582507149a6c90594e7795370/librt-0.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c6e4c10761ddbc0d67d2f6e2753daf99908db85d8b901729bf2bf5eaa60e0567", size = 216721, upload-time = "2026-02-12T14:52:08.857Z" }, + { url = "https://files.pythonhosted.org/packages/c4/52/17cbcf9b7a1bae5016d9d3561bc7169b32c3bd216c47d934d3f270602c0c/librt-0.8.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:ba581acad5ac8f33e2ff1746e8a57e001b47c6721873121bf8bbcf7ba8bd3aa4", size = 214790, upload-time = "2026-02-12T14:52:10.033Z" }, + { url = "https://files.pythonhosted.org/packages/2a/2d/010a236e8dc4d717dd545c46fd036dcced2c7ede71ef85cf55325809ff92/librt-0.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bdab762e2c0b48bab76f1a08acb3f4c77afd2123bedac59446aeaaeed3d086cf", size = 237384, upload-time = "2026-02-12T14:52:11.244Z" }, + { url = "https://files.pythonhosted.org/packages/38/14/f1c0eff3df8760dee761029efb72991c554d9f3282f1048e8c3d0eb60997/librt-0.8.0-cp310-cp310-win32.whl", hash = "sha256:6a3146c63220d814c4a2c7d6a1eacc8d5c14aed0ff85115c1dfea868080cd18f", size = 54289, upload-time = "2026-02-12T14:52:12.798Z" }, + { url = "https://files.pythonhosted.org/packages/2f/0b/2684d473e64890882729f91866ed97ccc0a751a0afc3b4bf1a7b57094dbb/librt-0.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:bbebd2bba5c6ae02907df49150e55870fdd7440d727b6192c46b6f754723dde9", size = 61347, upload-time = "2026-02-12T14:52:13.793Z" }, + { url = "https://files.pythonhosted.org/packages/51/e9/42af181c89b65abfd557c1b017cba5b82098eef7bf26d1649d82ce93ccc7/librt-0.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ce33a9778e294507f3a0e3468eccb6a698b5166df7db85661543eca1cfc5369", size = 65314, upload-time = "2026-02-12T14:52:14.778Z" }, + { url = "https://files.pythonhosted.org/packages/9d/4a/15a847fca119dc0334a4b8012b1e15fdc5fc19d505b71e227eaf1bcdba09/librt-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8070aa3368559de81061ef752770d03ca1f5fc9467d4d512d405bd0483bfffe6", size = 68015, upload-time = "2026-02-12T14:52:15.797Z" }, + { url = "https://files.pythonhosted.org/packages/e1/87/ffc8dbd6ab68dd91b736c88529411a6729649d2b74b887f91f3aaff8d992/librt-0.8.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:20f73d4fecba969efc15cdefd030e382502d56bb6f1fc66b580cce582836c9fa", size = 194508, upload-time = "2026-02-12T14:52:16.835Z" }, + { url = "https://files.pythonhosted.org/packages/89/92/a7355cea28d6c48ff6ff5083ac4a2a866fb9b07b786aa70d1f1116680cd5/librt-0.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a512c88900bdb1d448882f5623a0b1ad27ba81a9bd75dacfe17080b72272ca1f", size = 205630, upload-time = "2026-02-12T14:52:18.58Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5e/54509038d7ac527828db95b8ba1c8f5d2649bc32fd8f39b1718ec9957dce/librt-0.8.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:015e2dde6e096d27c10238bf9f6492ba6c65822dfb69d2bf74c41a8e88b7ddef", size = 218289, upload-time = "2026-02-12T14:52:20.134Z" }, + { url = "https://files.pythonhosted.org/packages/6d/17/0ee0d13685cefee6d6f2d47bb643ddad3c62387e2882139794e6a5f1288a/librt-0.8.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1c25a131013eadd3c600686a0c0333eb2896483cbc7f65baa6a7ee761017aef9", size = 211508, upload-time = "2026-02-12T14:52:21.413Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a8/1714ef6e9325582e3727de3be27e4c1b2f428ea411d09f1396374180f130/librt-0.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:21b14464bee0b604d80a638cf1ee3148d84ca4cc163dcdcecb46060c1b3605e4", size = 219129, upload-time = "2026-02-12T14:52:22.61Z" }, + { url = "https://files.pythonhosted.org/packages/89/d3/2d9fe353edff91cdc0ece179348054a6fa61f3de992c44b9477cb973509b/librt-0.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:05a3dd3f116747f7e1a2b475ccdc6fb637fd4987126d109e03013a79d40bf9e6", size = 213126, upload-time = "2026-02-12T14:52:23.819Z" }, + { url = "https://files.pythonhosted.org/packages/ad/8e/9f5c60444880f6ad50e3ff7475e5529e787797e7f3ad5432241633733b92/librt-0.8.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:fa37f99bff354ff191c6bcdffbc9d7cdd4fc37faccfc9be0ef3a4fd5613977da", size = 212279, upload-time = "2026-02-12T14:52:25.034Z" }, + { url = "https://files.pythonhosted.org/packages/fe/eb/d4a2cfa647da3022ae977f50d7eda1d91f70d7d1883cf958a4b6ef689eab/librt-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1566dbb9d1eb0987264c9b9460d212e809ba908d2f4a3999383a84d765f2f3f1", size = 234654, upload-time = "2026-02-12T14:52:26.204Z" }, + { url = "https://files.pythonhosted.org/packages/6a/31/26b978861c7983b036a3aea08bdbb2ec32bbaab1ad1d57c5e022be59afc1/librt-0.8.0-cp311-cp311-win32.whl", hash = "sha256:70defb797c4d5402166787a6b3c66dfb3fa7f93d118c0509ffafa35a392f4258", size = 54603, upload-time = "2026-02-12T14:52:27.342Z" }, + { url = "https://files.pythonhosted.org/packages/d0/78/f194ed7c48dacf875677e749c5d0d1d69a9daa7c994314a39466237fb1be/librt-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:db953b675079884ffda33d1dca7189fb961b6d372153750beb81880384300817", size = 61730, upload-time = "2026-02-12T14:52:28.31Z" }, + { url = "https://files.pythonhosted.org/packages/97/ee/ad71095478d02137b6f49469dc808c595cfe89b50985f6b39c5345f0faab/librt-0.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:75d1a8cab20b2043f03f7aab730551e9e440adc034d776f15f6f8d582b0a5ad4", size = 52274, upload-time = "2026-02-12T14:52:29.345Z" }, + { url = "https://files.pythonhosted.org/packages/fb/53/f3bc0c4921adb0d4a5afa0656f2c0fbe20e18e3e0295e12985b9a5dc3f55/librt-0.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:17269dd2745dbe8e42475acb28e419ad92dfa38214224b1b01020b8cac70b645", size = 66511, upload-time = "2026-02-12T14:52:30.34Z" }, + { url = "https://files.pythonhosted.org/packages/89/4b/4c96357432007c25a1b5e363045373a6c39481e49f6ba05234bb59a839c1/librt-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f4617cef654fca552f00ce5ffdf4f4b68770f18950e4246ce94629b789b92467", size = 68628, upload-time = "2026-02-12T14:52:31.491Z" }, + { url = "https://files.pythonhosted.org/packages/47/16/52d75374d1012e8fc709216b5eaa25f471370e2a2331b8be00f18670a6c7/librt-0.8.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5cb11061a736a9db45e3c1293cfcb1e3caf205912dfa085734ba750f2197ff9a", size = 198941, upload-time = "2026-02-12T14:52:32.489Z" }, + { url = "https://files.pythonhosted.org/packages/fc/11/d5dd89e5a2228567b1228d8602d896736247424484db086eea6b8010bcba/librt-0.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4bb00bd71b448f16749909b08a0ff16f58b079e2261c2e1000f2bbb2a4f0a45", size = 210009, upload-time = "2026-02-12T14:52:33.634Z" }, + { url = "https://files.pythonhosted.org/packages/49/d8/fc1a92a77c3020ee08ce2dc48aed4b42ab7c30fb43ce488d388673b0f164/librt-0.8.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95a719a049f0eefaf1952673223cf00d442952273cbd20cf2ed7ec423a0ef58d", size = 224461, upload-time = "2026-02-12T14:52:34.868Z" }, + { url = "https://files.pythonhosted.org/packages/7f/98/eb923e8b028cece924c246104aa800cf72e02d023a8ad4ca87135b05a2fe/librt-0.8.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bd32add59b58fba3439d48d6f36ac695830388e3da3e92e4fc26d2d02670d19c", size = 217538, upload-time = "2026-02-12T14:52:36.078Z" }, + { url = "https://files.pythonhosted.org/packages/fd/67/24e80ab170674a1d8ee9f9a83081dca4635519dbd0473b8321deecddb5be/librt-0.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4f764b2424cb04524ff7a486b9c391e93f93dc1bd8305b2136d25e582e99aa2f", size = 225110, upload-time = "2026-02-12T14:52:37.301Z" }, + { url = "https://files.pythonhosted.org/packages/d8/c7/6fbdcbd1a6e5243c7989c21d68ab967c153b391351174b4729e359d9977f/librt-0.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f04ca50e847abc486fa8f4107250566441e693779a5374ba211e96e238f298b9", size = 217758, upload-time = "2026-02-12T14:52:38.89Z" }, + { url = "https://files.pythonhosted.org/packages/4b/bd/4d6b36669db086e3d747434430073e14def032dd58ad97959bf7e2d06c67/librt-0.8.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9ab3a3475a55b89b87ffd7e6665838e8458e0b596c22e0177e0f961434ec474a", size = 218384, upload-time = "2026-02-12T14:52:40.637Z" }, + { url = "https://files.pythonhosted.org/packages/50/2d/afe966beb0a8f179b132f3e95c8dd90738a23e9ebdba10f89a3f192f9366/librt-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e36a8da17134ffc29373775d88c04832f9ecfab1880470661813e6c7991ef79", size = 241187, upload-time = "2026-02-12T14:52:43.55Z" }, + { url = "https://files.pythonhosted.org/packages/02/d0/6172ea4af2b538462785ab1a68e52d5c99cfb9866a7caf00fdf388299734/librt-0.8.0-cp312-cp312-win32.whl", hash = "sha256:4eb5e06ebcc668677ed6389164f52f13f71737fc8be471101fa8b4ce77baeb0c", size = 54914, upload-time = "2026-02-12T14:52:44.676Z" }, + { url = "https://files.pythonhosted.org/packages/d4/cb/ceb6ed6175612a4337ad49fb01ef594712b934b4bc88ce8a63554832eb44/librt-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:0a33335eb59921e77c9acc05d0e654e4e32e45b014a4d61517897c11591094f8", size = 62020, upload-time = "2026-02-12T14:52:45.676Z" }, + { url = "https://files.pythonhosted.org/packages/f1/7e/61701acbc67da74ce06ddc7ba9483e81c70f44236b2d00f6a4bfee1aacbf/librt-0.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:24a01c13a2a9bdad20997a4443ebe6e329df063d1978bbe2ebbf637878a46d1e", size = 52443, upload-time = "2026-02-12T14:52:47.218Z" }, + { url = "https://files.pythonhosted.org/packages/6d/32/3edb0bcb4113a9c8bdcd1750663a54565d255027657a5df9d90f13ee07fa/librt-0.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7f820210e21e3a8bf8fde2ae3c3d10106d4de9ead28cbfdf6d0f0f41f5b12fa1", size = 66522, upload-time = "2026-02-12T14:52:48.219Z" }, + { url = "https://files.pythonhosted.org/packages/30/ab/e8c3d05e281f5d405ebdcc5bc8ab36df23e1a4b40ac9da8c3eb9928b72b9/librt-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4831c44b8919e75ca0dfb52052897c1ef59fdae19d3589893fbd068f1e41afbf", size = 68658, upload-time = "2026-02-12T14:52:50.351Z" }, + { url = "https://files.pythonhosted.org/packages/7c/d3/74a206c47b7748bbc8c43942de3ed67de4c231156e148b4f9250869593df/librt-0.8.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:88c6e75540f1f10f5e0fc5e87b4b6c290f0e90d1db8c6734f670840494764af8", size = 199287, upload-time = "2026-02-12T14:52:51.938Z" }, + { url = "https://files.pythonhosted.org/packages/fa/29/ef98a9131cf12cb95771d24e4c411fda96c89dc78b09c2de4704877ebee4/librt-0.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9646178cd794704d722306c2c920c221abbf080fede3ba539d5afdec16c46dad", size = 210293, upload-time = "2026-02-12T14:52:53.128Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3e/89b4968cb08c53d4c2d8b02517081dfe4b9e07a959ec143d333d76899f6c/librt-0.8.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e1af31a710e17891d9adf0dbd9a5fcd94901a3922a96499abdbf7ce658f4e01", size = 224801, upload-time = "2026-02-12T14:52:54.367Z" }, + { url = "https://files.pythonhosted.org/packages/6d/28/f38526d501f9513f8b48d78e6be4a241e15dd4b000056dc8b3f06ee9ce5d/librt-0.8.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:507e94f4bec00b2f590fbe55f48cd518a208e2474a3b90a60aa8f29136ddbada", size = 218090, upload-time = "2026-02-12T14:52:55.758Z" }, + { url = "https://files.pythonhosted.org/packages/02/ec/64e29887c5009c24dc9c397116c680caffc50286f62bd99c39e3875a2854/librt-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f1178e0de0c271231a660fbef9be6acdfa1d596803464706862bef6644cc1cae", size = 225483, upload-time = "2026-02-12T14:52:57.375Z" }, + { url = "https://files.pythonhosted.org/packages/ee/16/7850bdbc9f1a32d3feff2708d90c56fc0490b13f1012e438532781aa598c/librt-0.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:71fc517efc14f75c2f74b1f0a5d5eb4a8e06aa135c34d18eaf3522f4a53cd62d", size = 218226, upload-time = "2026-02-12T14:52:58.534Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4a/166bffc992d65ddefa7c47052010a87c059b44a458ebaf8f5eba384b0533/librt-0.8.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0583aef7e9a720dd40f26a2ad5a1bf2ccbb90059dac2b32ac516df232c701db3", size = 218755, upload-time = "2026-02-12T14:52:59.701Z" }, + { url = "https://files.pythonhosted.org/packages/da/5d/9aeee038bcc72a9cfaaee934463fe9280a73c5440d36bd3175069d2cb97b/librt-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d0f76fc73480d42285c609c0ea74d79856c160fa828ff9aceab574ea4ecfd7b", size = 241617, upload-time = "2026-02-12T14:53:00.966Z" }, + { url = "https://files.pythonhosted.org/packages/64/ff/2bec6b0296b9d0402aa6ec8540aa19ebcb875d669c37800cb43d10d9c3a3/librt-0.8.0-cp313-cp313-win32.whl", hash = "sha256:e79dbc8f57de360f0ed987dc7de7be814b4803ef0e8fc6d3ff86e16798c99935", size = 54966, upload-time = "2026-02-12T14:53:02.042Z" }, + { url = "https://files.pythonhosted.org/packages/08/8d/bf44633b0182996b2c7ea69a03a5c529683fa1f6b8e45c03fe874ff40d56/librt-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:25b3e667cbfc9000c4740b282df599ebd91dbdcc1aa6785050e4c1d6be5329ab", size = 62000, upload-time = "2026-02-12T14:53:03.822Z" }, + { url = "https://files.pythonhosted.org/packages/5c/fd/c6472b8e0eac0925001f75e366cf5500bcb975357a65ef1f6b5749389d3a/librt-0.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:e9a3a38eb4134ad33122a6d575e6324831f930a771d951a15ce232e0237412c2", size = 52496, upload-time = "2026-02-12T14:53:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/e0/13/79ebfe30cd273d7c0ce37a5f14dc489c5fb8b722a008983db2cfd57270bb/librt-0.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:421765e8c6b18e64d21c8ead315708a56fc24f44075059702e421d164575fdda", size = 66078, upload-time = "2026-02-12T14:53:06.085Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8f/d11eca40b62a8d5e759239a80636386ef88adecb10d1a050b38cc0da9f9e/librt-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:48f84830a8f8ad7918afd743fd7c4eb558728bceab7b0e38fd5a5cf78206a556", size = 68309, upload-time = "2026-02-12T14:53:07.121Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b4/f12ee70a3596db40ff3c88ec9eaa4e323f3b92f77505b4d900746706ec6a/librt-0.8.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9f09d4884f882baa39a7e36bbf3eae124c4ca2a223efb91e567381d1c55c6b06", size = 196804, upload-time = "2026-02-12T14:53:08.164Z" }, + { url = "https://files.pythonhosted.org/packages/8b/7e/70dbbdc0271fd626abe1671ad117bcd61a9a88cdc6a10ccfbfc703db1873/librt-0.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:693697133c3b32aa9b27f040e3691be210e9ac4d905061859a9ed519b1d5a376", size = 206915, upload-time = "2026-02-12T14:53:09.333Z" }, + { url = "https://files.pythonhosted.org/packages/79/13/6b9e05a635d4327608d06b3c1702166e3b3e78315846373446cf90d7b0bf/librt-0.8.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5512aae4648152abaf4d48b59890503fcbe86e85abc12fb9b096fe948bdd816", size = 221200, upload-time = "2026-02-12T14:53:10.68Z" }, + { url = "https://files.pythonhosted.org/packages/35/6c/e19a3ac53e9414de43a73d7507d2d766cd22d8ca763d29a4e072d628db42/librt-0.8.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:995d24caa6bbb34bcdd4a41df98ac6d1af637cfa8975cb0790e47d6623e70e3e", size = 214640, upload-time = "2026-02-12T14:53:12.342Z" }, + { url = "https://files.pythonhosted.org/packages/30/f0/23a78464788619e8c70f090cfd099cce4973eed142c4dccb99fc322283fd/librt-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b9aef96d7593584e31ef6ac1eb9775355b0099fee7651fae3a15bc8657b67b52", size = 221980, upload-time = "2026-02-12T14:53:13.603Z" }, + { url = "https://files.pythonhosted.org/packages/03/32/38e21420c5d7aa8a8bd2c7a7d5252ab174a5a8aaec8b5551968979b747bf/librt-0.8.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:4f6e975377fbc4c9567cb33ea9ab826031b6c7ec0515bfae66a4fb110d40d6da", size = 215146, upload-time = "2026-02-12T14:53:14.8Z" }, + { url = "https://files.pythonhosted.org/packages/bb/00/bd9ecf38b1824c25240b3ad982fb62c80f0a969e6679091ba2b3afb2b510/librt-0.8.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:daae5e955764be8fd70a93e9e5133c75297f8bce1e802e1d3683b98f77e1c5ab", size = 215203, upload-time = "2026-02-12T14:53:16.087Z" }, + { url = "https://files.pythonhosted.org/packages/b9/60/7559bcc5279d37810b98d4a52616febd7b8eef04391714fd6bdf629598b1/librt-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7bd68cebf3131bb920d5984f75fe302d758db33264e44b45ad139385662d7bc3", size = 237937, upload-time = "2026-02-12T14:53:17.236Z" }, + { url = "https://files.pythonhosted.org/packages/41/cc/be3e7da88f1abbe2642672af1dc00a0bccece11ca60241b1883f3018d8d5/librt-0.8.0-cp314-cp314-win32.whl", hash = "sha256:1e6811cac1dcb27ca4c74e0ca4a5917a8e06db0d8408d30daee3a41724bfde7a", size = 50685, upload-time = "2026-02-12T14:53:18.888Z" }, + { url = "https://files.pythonhosted.org/packages/38/27/e381d0df182a8f61ef1f6025d8b138b3318cc9d18ad4d5f47c3bf7492523/librt-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:178707cda89d910c3b28bf5aa5f69d3d4734e0f6ae102f753ad79edef83a83c7", size = 57872, upload-time = "2026-02-12T14:53:19.942Z" }, + { url = "https://files.pythonhosted.org/packages/c5/0c/ca9dfdf00554a44dea7d555001248269a4bab569e1590a91391feb863fa4/librt-0.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:3e8b77b5f54d0937b26512774916041756c9eb3e66f1031971e626eea49d0bf4", size = 48056, upload-time = "2026-02-12T14:53:21.473Z" }, + { url = "https://files.pythonhosted.org/packages/f2/ed/6cc9c4ad24f90c8e782193c7b4a857408fd49540800613d1356c63567d7b/librt-0.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:789911e8fa40a2e82f41120c936b1965f3213c67f5a483fc5a41f5839a05dcbb", size = 68307, upload-time = "2026-02-12T14:53:22.498Z" }, + { url = "https://files.pythonhosted.org/packages/84/d8/0e94292c6b3e00b6eeea39dd44d5703d1ec29b6dafce7eea19dc8f1aedbd/librt-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2b37437e7e4ef5e15a297b36ba9e577f73e29564131d86dd75875705e97402b5", size = 70999, upload-time = "2026-02-12T14:53:23.603Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f4/6be1afcbdeedbdbbf54a7c9d73ad43e1bf36897cebf3978308cd64922e02/librt-0.8.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:671a6152edf3b924d98a5ed5e6982ec9cb30894085482acadce0975f031d4c5c", size = 220782, upload-time = "2026-02-12T14:53:25.133Z" }, + { url = "https://files.pythonhosted.org/packages/f0/8d/f306e8caa93cfaf5c6c9e0d940908d75dc6af4fd856baa5535c922ee02b1/librt-0.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8992ca186a1678107b0af3d0c9303d8c7305981b9914989b9788319ed4d89546", size = 235420, upload-time = "2026-02-12T14:53:27.047Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f2/65d86bd462e9c351326564ca805e8457442149f348496e25ccd94583ffa2/librt-0.8.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:001e5330093d887b8b9165823eca6c5c4db183fe4edea4fdc0680bbac5f46944", size = 246452, upload-time = "2026-02-12T14:53:28.341Z" }, + { url = "https://files.pythonhosted.org/packages/03/94/39c88b503b4cb3fcbdeb3caa29672b6b44ebee8dcc8a54d49839ac280f3f/librt-0.8.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d920789eca7ef71df7f31fd547ec0d3002e04d77f30ba6881e08a630e7b2c30e", size = 238891, upload-time = "2026-02-12T14:53:29.625Z" }, + { url = "https://files.pythonhosted.org/packages/e3/c6/6c0d68190893d01b71b9569b07a1c811e280c0065a791249921c83dc0290/librt-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:82fb4602d1b3e303a58bfe6165992b5a78d823ec646445356c332cd5f5bbaa61", size = 250249, upload-time = "2026-02-12T14:53:30.93Z" }, + { url = "https://files.pythonhosted.org/packages/52/7a/f715ed9e039035d0ea637579c3c0155ab3709a7046bc408c0fb05d337121/librt-0.8.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:4d3e38797eb482485b486898f89415a6ab163bc291476bd95712e42cf4383c05", size = 240642, upload-time = "2026-02-12T14:53:32.174Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3c/609000a333debf5992efe087edc6467c1fdbdddca5b610355569bbea9589/librt-0.8.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a905091a13e0884701226860836d0386b88c72ce5c2fdfba6618e14c72be9f25", size = 239621, upload-time = "2026-02-12T14:53:33.39Z" }, + { url = "https://files.pythonhosted.org/packages/b9/df/87b0673d5c395a8f34f38569c116c93142d4dc7e04af2510620772d6bd4f/librt-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:375eda7acfce1f15f5ed56cfc960669eefa1ec8732e3e9087c3c4c3f2066759c", size = 262986, upload-time = "2026-02-12T14:53:34.617Z" }, + { url = "https://files.pythonhosted.org/packages/09/7f/6bbbe9dcda649684773aaea78b87fff4d7e59550fbc2877faa83612087a3/librt-0.8.0-cp314-cp314t-win32.whl", hash = "sha256:2ccdd20d9a72c562ffb73098ac411de351b53a6fbb3390903b2d33078ef90447", size = 51328, upload-time = "2026-02-12T14:53:36.15Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f3/e1981ab6fa9b41be0396648b5850267888a752d025313a9e929c4856208e/librt-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:25e82d920d4d62ad741592fcf8d0f3bda0e3fc388a184cb7d2f566c681c5f7b9", size = 58719, upload-time = "2026-02-12T14:53:37.183Z" }, + { url = "https://files.pythonhosted.org/packages/94/d1/433b3c06e78f23486fe4fdd19bc134657eb30997d2054b0dbf52bbf3382e/librt-0.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:92249938ab744a5890580d3cb2b22042f0dce71cdaa7c1369823df62bedf7cbc", size = 48753, upload-time = "2026-02-12T14:53:38.539Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "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.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/0b/19348d4c98980c4851d2f943f8ebafdece2ae7ef737adcfa5994ce8e5f10/multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5", size = 77176, upload-time = "2026-01-26T02:42:59.784Z" }, + { url = "https://files.pythonhosted.org/packages/ef/04/9de3f8077852e3d438215c81e9b691244532d2e05b4270e89ce67b7d103c/multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8", size = 44996, upload-time = "2026-01-26T02:43:01.674Z" }, + { url = "https://files.pythonhosted.org/packages/31/5c/08c7f7fe311f32e83f7621cd3f99d805f45519cd06fafb247628b861da7d/multidict-6.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdea2e7b2456cfb6694fb113066fd0ec7ea4d67e3a35e1f4cbeea0b448bf5872", size = 44631, upload-time = "2026-01-26T02:43:03.169Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7f/0e3b1390ae772f27501199996b94b52ceeb64fe6f9120a32c6c3f6b781be/multidict-6.7.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17207077e29342fdc2c9a82e4b306f1127bf1ea91f8b71e02d4798a70bb99991", size = 242561, upload-time = "2026-01-26T02:43:04.733Z" }, + { url = "https://files.pythonhosted.org/packages/dd/f4/8719f4f167586af317b69dd3e90f913416c91ca610cac79a45c53f590312/multidict-6.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4f49cb5661344764e4c7c7973e92a47a59b8fc19b6523649ec9dc4960e58a03", size = 242223, upload-time = "2026-01-26T02:43:06.695Z" }, + { url = "https://files.pythonhosted.org/packages/47/ab/7c36164cce64a6ad19c6d9a85377b7178ecf3b89f8fd589c73381a5eedfd/multidict-6.7.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a9fc4caa29e2e6ae408d1c450ac8bf19892c5fca83ee634ecd88a53332c59981", size = 222322, upload-time = "2026-01-26T02:43:08.472Z" }, + { url = "https://files.pythonhosted.org/packages/f5/79/a25add6fb38035b5337bc5734f296d9afc99163403bbcf56d4170f97eb62/multidict-6.7.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c5f0c21549ab432b57dcc82130f388d84ad8179824cc3f223d5e7cfbfd4143f6", size = 254005, upload-time = "2026-01-26T02:43:10.127Z" }, + { url = "https://files.pythonhosted.org/packages/4a/7b/64a87cf98e12f756fc8bd444b001232ffff2be37288f018ad0d3f0aae931/multidict-6.7.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7dfb78d966b2c906ae1d28ccf6e6712a3cd04407ee5088cd276fe8cb42186190", size = 251173, upload-time = "2026-01-26T02:43:11.731Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ac/b605473de2bb404e742f2cc3583d12aedb2352a70e49ae8fce455b50c5aa/multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b0d9b91d1aa44db9c1f1ecd0d9d2ae610b2f4f856448664e01a3b35899f3f92", size = 243273, upload-time = "2026-01-26T02:43:13.063Z" }, + { url = "https://files.pythonhosted.org/packages/03/65/11492d6a0e259783720f3bc1d9ea55579a76f1407e31ed44045c99542004/multidict-6.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dd96c01a9dcd4889dcfcf9eb5544ca0c77603f239e3ffab0524ec17aea9a93ee", size = 238956, upload-time = "2026-01-26T02:43:14.843Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a7/7ee591302af64e7c196fb63fe856c788993c1372df765102bd0448e7e165/multidict-6.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:067343c68cd6612d375710f895337b3a98a033c94f14b9a99eff902f205424e2", size = 233477, upload-time = "2026-01-26T02:43:16.025Z" }, + { url = "https://files.pythonhosted.org/packages/9c/99/c109962d58756c35fd9992fed7f2355303846ea2ff054bb5f5e9d6b888de/multidict-6.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5884a04f4ff56c6120f6ccf703bdeb8b5079d808ba604d4d53aec0d55dc33568", size = 243615, upload-time = "2026-01-26T02:43:17.84Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5f/1973e7c771c86e93dcfe1c9cc55a5481b610f6614acfc28c0d326fe6bfad/multidict-6.7.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8affcf1c98b82bc901702eb73b6947a1bfa170823c153fe8a47b5f5f02e48e40", size = 249930, upload-time = "2026-01-26T02:43:19.06Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a5/f170fc2268c3243853580203378cd522446b2df632061e0a5409817854c7/multidict-6.7.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d17522c37d03e85c8098ec8431636309b2682cf12e58f4dbc76121fb50e4962", size = 243807, upload-time = "2026-01-26T02:43:20.286Z" }, + { url = "https://files.pythonhosted.org/packages/de/01/73856fab6d125e5bc652c3986b90e8699a95e84b48d72f39ade6c0e74a8c/multidict-6.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24c0cf81544ca5e17cfcb6e482e7a82cd475925242b308b890c9452a074d4505", size = 239103, upload-time = "2026-01-26T02:43:21.508Z" }, + { url = "https://files.pythonhosted.org/packages/e7/46/f1220bd9944d8aa40d8ccff100eeeee19b505b857b6f603d6078cb5315b0/multidict-6.7.1-cp310-cp310-win32.whl", hash = "sha256:d82dd730a95e6643802f4454b8fdecdf08667881a9c5670db85bc5a56693f122", size = 41416, upload-time = "2026-01-26T02:43:22.703Z" }, + { url = "https://files.pythonhosted.org/packages/68/00/9b38e272a770303692fc406c36e1a4c740f401522d5787691eb38a8925a8/multidict-6.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf37cbe5ced48d417ba045aca1b21bafca67489452debcde94778a576666a1df", size = 46022, upload-time = "2026-01-26T02:43:23.77Z" }, + { url = "https://files.pythonhosted.org/packages/64/65/d8d42490c02ee07b6bbe00f7190d70bb4738b3cce7629aaf9f213ef730dd/multidict-6.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:59bc83d3f66b41dac1e7460aac1d196edc70c9ba3094965c467715a70ecb46db", size = 43238, upload-time = "2026-01-26T02:43:24.882Z" }, + { url = "https://files.pythonhosted.org/packages/ce/f1/a90635c4f88fb913fbf4ce660b83b7445b7a02615bda034b2f8eb38fd597/multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d", size = 76626, upload-time = "2026-01-26T02:43:26.485Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e", size = 44706, upload-time = "2026-01-26T02:43:27.607Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855", size = 44356, upload-time = "2026-01-26T02:43:28.661Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d2/0a36c8473f0cbaeadd5db6c8b72d15bbceeec275807772bfcd059bef487d/multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3", size = 244355, upload-time = "2026-01-26T02:43:31.165Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/8c65be997fd7dd311b7d39c7b6e71a0cb449bad093761481eccbbe4b42a2/multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e", size = 246433, upload-time = "2026-01-26T02:43:32.581Z" }, + { url = "https://files.pythonhosted.org/packages/01/fb/4dbd7e848d2799c6a026ec88ad39cf2b8416aa167fcc903baa55ecaa045c/multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a", size = 225376, upload-time = "2026-01-26T02:43:34.417Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/4a3a6341eac3830f6053062f8fbc9a9e54407c80755b3f05bc427295c2d0/multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8", size = 257365, upload-time = "2026-01-26T02:43:35.741Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a2/dd575a69c1aa206e12d27d0770cdf9b92434b48a9ef0cd0d1afdecaa93c4/multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0", size = 254747, upload-time = "2026-01-26T02:43:36.976Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144", size = 246293, upload-time = "2026-01-26T02:43:38.258Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/23466059dc3854763423d0ad6c0f3683a379d97673b1b89ec33826e46728/multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49", size = 242962, upload-time = "2026-01-26T02:43:40.034Z" }, + { url = "https://files.pythonhosted.org/packages/1f/67/51dd754a3524d685958001e8fa20a0f5f90a6a856e0a9dcabff69be3dbb7/multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71", size = 237360, upload-time = "2026-01-26T02:43:41.752Z" }, + { url = "https://files.pythonhosted.org/packages/64/3f/036dfc8c174934d4b55d86ff4f978e558b0e585cef70cfc1ad01adc6bf18/multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3", size = 245940, upload-time = "2026-01-26T02:43:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/3d/20/6214d3c105928ebc353a1c644a6ef1408bc5794fcb4f170bb524a3c16311/multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c", size = 253502, upload-time = "2026-01-26T02:43:44.371Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e2/c653bc4ae1be70a0f836b82172d643fcf1dade042ba2676ab08ec08bff0f/multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0", size = 247065, upload-time = "2026-01-26T02:43:45.745Z" }, + { url = "https://files.pythonhosted.org/packages/c8/11/a854b4154cd3bd8b1fd375e8a8ca9d73be37610c361543d56f764109509b/multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa", size = 241870, upload-time = "2026-01-26T02:43:47.054Z" }, + { url = "https://files.pythonhosted.org/packages/13/bf/9676c0392309b5fdae322333d22a829715b570edb9baa8016a517b55b558/multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a", size = 41302, upload-time = "2026-01-26T02:43:48.753Z" }, + { url = "https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b", size = 45981, upload-time = "2026-01-26T02:43:49.921Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/9dd5305253fa00cd3c7555dbef69d5bf4133debc53b87ab8d6a44d411665/multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6", size = 43159, upload-time = "2026-01-26T02:43:51.635Z" }, + { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, + { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, + { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, + { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, + { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, + { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, + { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" }, + { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, + { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" }, + { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" }, + { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" }, + { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" }, + { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" }, + { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, + { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, + { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, + { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" }, + { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" }, + { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, + { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, + { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, + { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, + { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, + { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, + { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, + { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, + { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, + { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, + { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, + { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, + { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, +] + +[[package]] +name = "mypy" +version = "1.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/63/e499890d8e39b1ff2df4c0c6ce5d371b6844ee22b8250687a99fd2f657a8/mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec", size = 13101333, upload-time = "2025-12-15T05:03:03.28Z" }, + { url = "https://files.pythonhosted.org/packages/72/4b/095626fc136fba96effc4fd4a82b41d688ab92124f8c4f7564bffe5cf1b0/mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b", size = 12164102, upload-time = "2025-12-15T05:02:33.611Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/952928dd081bf88a83a5ccd49aaecfcd18fd0d2710c7ff07b8fb6f7032b9/mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6", size = 12765799, upload-time = "2025-12-15T05:03:28.44Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0d/93c2e4a287f74ef11a66fb6d49c7a9f05e47b0a4399040e6719b57f500d2/mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74", size = 13522149, upload-time = "2025-12-15T05:02:36.011Z" }, + { url = "https://files.pythonhosted.org/packages/7b/0e/33a294b56aaad2b338d203e3a1d8b453637ac36cb278b45005e0901cf148/mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1", size = 13810105, upload-time = "2025-12-15T05:02:40.327Z" }, + { url = "https://files.pythonhosted.org/packages/0e/fd/3e82603a0cb66b67c5e7abababce6bf1a929ddf67bf445e652684af5c5a0/mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac", size = 10057200, upload-time = "2025-12-15T05:02:51.012Z" }, + { url = "https://files.pythonhosted.org/packages/ef/47/6b3ebabd5474d9cdc170d1342fbf9dddc1b0ec13ec90bf9004ee6f391c31/mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288", size = 13028539, upload-time = "2025-12-15T05:03:44.129Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a6/ac7c7a88a3c9c54334f53a941b765e6ec6c4ebd65d3fe8cdcfbe0d0fd7db/mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab", size = 12083163, upload-time = "2025-12-15T05:03:37.679Z" }, + { url = "https://files.pythonhosted.org/packages/67/af/3afa9cf880aa4a2c803798ac24f1d11ef72a0c8079689fac5cfd815e2830/mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6", size = 12687629, upload-time = "2025-12-15T05:02:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/2d/46/20f8a7114a56484ab268b0ab372461cb3a8f7deed31ea96b83a4e4cfcfca/mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331", size = 13436933, upload-time = "2025-12-15T05:03:15.606Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f8/33b291ea85050a21f15da910002460f1f445f8007adb29230f0adea279cb/mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925", size = 13661754, upload-time = "2025-12-15T05:02:26.731Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a3/47cbd4e85bec4335a9cd80cf67dbc02be21b5d4c9c23ad6b95d6c5196bac/mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042", size = 10055772, upload-time = "2025-12-15T05:03:26.179Z" }, + { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" }, + { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" }, + { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, + { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, + { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, + { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, + { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, + { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, + { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, + { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, + { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, + { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, +] + +[[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.39.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/97/b9/3161be15bb8e3ad01be8be5a968a9237c3027c5be504362ff800fca3e442/opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c", size = 65767, upload-time = "2025-12-11T13:32:39.182Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/df/d3f1ddf4bb4cb50ed9b1139cc7b1c54c34a1e7ce8fd1b9a37c0d1551a6bd/opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950", size = 66356, upload-time = "2025-12-11T13:32:17.304Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.39.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-proto" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/9d/22d241b66f7bbde88a3bfa6847a351d2c46b84de23e71222c6aae25c7050/opentelemetry_exporter_otlp_proto_common-1.39.1.tar.gz", hash = "sha256:763370d4737a59741c89a67b50f9e39271639ee4afc999dadfe768541c027464", size = 20409, upload-time = "2025-12-11T13:32:40.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/02/ffc3e143d89a27ac21fd557365b98bd0653b98de8a101151d5805b5d4c33/opentelemetry_exporter_otlp_proto_common-1.39.1-py3-none-any.whl", hash = "sha256:08f8a5862d64cc3435105686d0216c1365dc5701f86844a8cd56597d0c764fde", size = 18366, upload-time = "2025-12-11T13:32:20.2Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.39.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/80/04/2a08fa9c0214ae38880df01e8bfae12b067ec0793446578575e5080d6545/opentelemetry_exporter_otlp_proto_http-1.39.1.tar.gz", hash = "sha256:31bdab9745c709ce90a49a0624c2bd445d31a28ba34275951a6a362d16a0b9cb", size = 17288, upload-time = "2025-12-11T13:32:42.029Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/f1/b27d3e2e003cd9a3592c43d099d2ed8d0a947c15281bf8463a256db0b46c/opentelemetry_exporter_otlp_proto_http-1.39.1-py3-none-any.whl", hash = "sha256:d9f5207183dd752a412c4cd564ca8875ececba13be6e9c6c370ffb752fd59985", size = 19641, upload-time = "2025-12-11T13:32:22.248Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.60b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "packaging" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/0f/7e6b713ac117c1f5e4e3300748af699b9902a2e5e34c9cf443dde25a01fa/opentelemetry_instrumentation-0.60b1.tar.gz", hash = "sha256:57ddc7974c6eb35865af0426d1a17132b88b2ed8586897fee187fd5b8944bd6a", size = 31706, upload-time = "2025-12-11T13:36:42.515Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/d2/6788e83c5c86a2690101681aeef27eeb2a6bf22df52d3f263a22cee20915/opentelemetry_instrumentation-0.60b1-py3-none-any.whl", hash = "sha256:04480db952b48fb1ed0073f822f0ee26012b7be7c3eac1a3793122737c78632d", size = 33096, upload-time = "2025-12-11T13:35:33.067Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-logging" +version = "0.60b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/a6/4515895b383113677fd2ad21813df5e56108a2df14ebb7916c962c9a0234/opentelemetry_instrumentation_logging-0.60b1.tar.gz", hash = "sha256:98f4b9c7aeb9314a30feee7c002c7ea9abea07c90df5f97fb058b850bc45b89a", size = 9968, upload-time = "2025-12-11T13:37:03.974Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/f9/8a4ce3901bc52277794e4b18c4ac43dc5929806eff01d22812364132f45f/opentelemetry_instrumentation_logging-0.60b1-py3-none-any.whl", hash = "sha256:f2e18cbc7e1dd3628c80e30d243897fdc93c5b7e0c8ae60abd2b9b6a99f82343", size = 12577, upload-time = "2025-12-11T13:36:08.123Z" }, +] + +[[package]] +name = "opentelemetry-proto" +version = "1.39.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/1d/f25d76d8260c156c40c97c9ed4511ec0f9ce353f8108ca6e7561f82a06b2/opentelemetry_proto-1.39.1.tar.gz", hash = "sha256:6c8e05144fc0d3ed4d22c2289c6b126e03bcd0e6a7da0f16cedd2e1c2772e2c8", size = 46152, upload-time = "2025-12-11T13:32:48.681Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/95/b40c96a7b5203005a0b03d8ce8cd212ff23f1793d5ba289c87a097571b18/opentelemetry_proto-1.39.1-py3-none-any.whl", hash = "sha256:22cdc78efd3b3765d09e68bfbd010d4fc254c9818afd0b6b423387d9dee46007", size = 72535, upload-time = "2025-12-11T13:32:33.866Z" }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.39.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/eb/fb/c76080c9ba07e1e8235d24cdcc4d125ef7aa3edf23eb4e497c2e50889adc/opentelemetry_sdk-1.39.1.tar.gz", hash = "sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6", size = 171460, upload-time = "2025-12-11T13:32:49.369Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/98/e91cf858f203d86f4eccdf763dcf01cf03f1dae80c3750f7e635bfa206b6/opentelemetry_sdk-1.39.1-py3-none-any.whl", hash = "sha256:4d5482c478513ecb0a5d938dcc61394e647066e0cc2676bee9f3af3f3f45f01c", size = 132565, upload-time = "2025-12-11T13:32:35.069Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.60b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/df/553f93ed38bf22f4b999d9be9c185adb558982214f33eae539d3b5cd0858/opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953", size = 137935, upload-time = "2025-12-11T13:32:50.487Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/5e/5958555e09635d09b75de3c4f8b9cae7335ca545d77392ffe7331534c402/opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl", hash = "sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb", size = 219982, upload-time = "2025-12-11T13:32:36.955Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pathspec" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.9.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, +] + +[[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.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/0e/934b541323035566a9af292dba85a195f7b78179114f2c6ebb24551118a9/propcache-0.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c2d1fa3201efaf55d730400d945b5b3ab6e672e100ba0f9a409d950ab25d7db", size = 79534, upload-time = "2025-10-08T19:46:02.083Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6b/db0d03d96726d995dc7171286c6ba9d8d14251f37433890f88368951a44e/propcache-0.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1eb2994229cc8ce7fe9b3db88f5465f5fd8651672840b2e426b88cdb1a30aac8", size = 45526, upload-time = "2025-10-08T19:46:03.884Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c3/82728404aea669e1600f304f2609cde9e665c18df5a11cdd57ed73c1dceb/propcache-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:66c1f011f45a3b33d7bcb22daed4b29c0c9e2224758b6be00686731e1b46f925", size = 47263, upload-time = "2025-10-08T19:46:05.405Z" }, + { url = "https://files.pythonhosted.org/packages/df/1b/39313ddad2bf9187a1432654c38249bab4562ef535ef07f5eb6eb04d0b1b/propcache-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a52009f2adffe195d0b605c25ec929d26b36ef986ba85244891dee3b294df21", size = 201012, upload-time = "2025-10-08T19:46:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/5b/01/f1d0b57d136f294a142acf97f4ed58c8e5b974c21e543000968357115011/propcache-0.4.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5d4e2366a9c7b837555cf02fb9be2e3167d333aff716332ef1b7c3a142ec40c5", size = 209491, upload-time = "2025-10-08T19:46:08.909Z" }, + { url = "https://files.pythonhosted.org/packages/a1/c8/038d909c61c5bb039070b3fb02ad5cccdb1dde0d714792e251cdb17c9c05/propcache-0.4.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d2b6caef873b4f09e26ea7e33d65f42b944837563a47a94719cc3544319a0db", size = 215319, upload-time = "2025-10-08T19:46:10.7Z" }, + { url = "https://files.pythonhosted.org/packages/08/57/8c87e93142b2c1fa2408e45695205a7ba05fb5db458c0bf5c06ba0e09ea6/propcache-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b16ec437a8c8a965ecf95739448dd938b5c7f56e67ea009f4300d8df05f32b7", size = 196856, upload-time = "2025-10-08T19:46:12.003Z" }, + { url = "https://files.pythonhosted.org/packages/42/df/5615fec76aa561987a534759b3686008a288e73107faa49a8ae5795a9f7a/propcache-0.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:296f4c8ed03ca7476813fe666c9ea97869a8d7aec972618671b33a38a5182ef4", size = 193241, upload-time = "2025-10-08T19:46:13.495Z" }, + { url = "https://files.pythonhosted.org/packages/d5/21/62949eb3a7a54afe8327011c90aca7e03547787a88fb8bd9726806482fea/propcache-0.4.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:1f0978529a418ebd1f49dad413a2b68af33f85d5c5ca5c6ca2a3bed375a7ac60", size = 190552, upload-time = "2025-10-08T19:46:14.938Z" }, + { url = "https://files.pythonhosted.org/packages/30/ee/ab4d727dd70806e5b4de96a798ae7ac6e4d42516f030ee60522474b6b332/propcache-0.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fd138803047fb4c062b1c1dd95462f5209456bfab55c734458f15d11da288f8f", size = 200113, upload-time = "2025-10-08T19:46:16.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0b/38b46208e6711b016aa8966a3ac793eee0d05c7159d8342aa27fc0bc365e/propcache-0.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8c9b3cbe4584636d72ff556d9036e0c9317fa27b3ac1f0f558e7e84d1c9c5900", size = 200778, upload-time = "2025-10-08T19:46:18.023Z" }, + { url = "https://files.pythonhosted.org/packages/cf/81/5abec54355ed344476bee711e9f04815d4b00a311ab0535599204eecc257/propcache-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f93243fdc5657247533273ac4f86ae106cc6445a0efacb9a1bfe982fcfefd90c", size = 193047, upload-time = "2025-10-08T19:46:19.449Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b6/1f237c04e32063cb034acd5f6ef34ef3a394f75502e72703545631ab1ef6/propcache-0.4.1-cp310-cp310-win32.whl", hash = "sha256:a0ee98db9c5f80785b266eb805016e36058ac72c51a064040f2bc43b61101cdb", size = 38093, upload-time = "2025-10-08T19:46:20.643Z" }, + { url = "https://files.pythonhosted.org/packages/a6/67/354aac4e0603a15f76439caf0427781bcd6797f370377f75a642133bc954/propcache-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:1cdb7988c4e5ac7f6d175a28a9aa0c94cb6f2ebe52756a3c0cda98d2809a9e37", size = 41638, upload-time = "2025-10-08T19:46:21.935Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e1/74e55b9fd1a4c209ff1a9a824bf6c8b3d1fc5a1ac3eabe23462637466785/propcache-0.4.1-cp310-cp310-win_arm64.whl", hash = "sha256:d82ad62b19645419fe79dd63b3f9253e15b30e955c0170e5cebc350c1844e581", size = 38229, upload-time = "2025-10-08T19:46:23.368Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d4/4e2c9aaf7ac2242b9358f98dccd8f90f2605402f5afeff6c578682c2c491/propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf", size = 80208, upload-time = "2025-10-08T19:46:24.597Z" }, + { url = "https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5", size = 45777, upload-time = "2025-10-08T19:46:25.733Z" }, + { url = "https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e", size = 47647, upload-time = "2025-10-08T19:46:27.304Z" }, + { url = "https://files.pythonhosted.org/packages/58/1a/3c62c127a8466c9c843bccb503d40a273e5cc69838805f322e2826509e0d/propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566", size = 214929, upload-time = "2025-10-08T19:46:28.62Z" }, + { url = "https://files.pythonhosted.org/packages/56/b9/8fa98f850960b367c4b8fe0592e7fc341daa7a9462e925228f10a60cf74f/propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165", size = 221778, upload-time = "2025-10-08T19:46:30.358Z" }, + { url = "https://files.pythonhosted.org/packages/46/a6/0ab4f660eb59649d14b3d3d65c439421cf2f87fe5dd68591cbe3c1e78a89/propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc", size = 228144, upload-time = "2025-10-08T19:46:32.607Z" }, + { url = "https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48", size = 210030, upload-time = "2025-10-08T19:46:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/40/e2/27e6feebb5f6b8408fa29f5efbb765cd54c153ac77314d27e457a3e993b7/propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570", size = 208252, upload-time = "2025-10-08T19:46:35.309Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f8/91c27b22ccda1dbc7967f921c42825564fa5336a01ecd72eb78a9f4f53c2/propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85", size = 202064, upload-time = "2025-10-08T19:46:36.993Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/7f00bd6bd1adba5aafe5f4a66390f243acab58eab24ff1a08bebb2ef9d40/propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e", size = 212429, upload-time = "2025-10-08T19:46:38.398Z" }, + { url = "https://files.pythonhosted.org/packages/84/89/fd108ba7815c1117ddca79c228f3f8a15fc82a73bca8b142eb5de13b2785/propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757", size = 216727, upload-time = "2025-10-08T19:46:39.732Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/3ec3f7e3173e73f1d600495d8b545b53802cbf35506e5732dd8578db3724/propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f", size = 205097, upload-time = "2025-10-08T19:46:41.025Z" }, + { url = "https://files.pythonhosted.org/packages/61/b0/b2631c19793f869d35f47d5a3a56fb19e9160d3c119f15ac7344fc3ccae7/propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1", size = 38084, upload-time = "2025-10-08T19:46:42.693Z" }, + { url = "https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6", size = 41637, upload-time = "2025-10-08T19:46:43.778Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e9/754f180cccd7f51a39913782c74717c581b9cc8177ad0e949f4d51812383/propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239", size = 38064, upload-time = "2025-10-08T19:46:44.872Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, + { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, + { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, + { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" }, + { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" }, + { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" }, + { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload-time = "2025-10-08T19:47:08.851Z" }, + { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" }, + { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" }, + { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" }, + { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload-time = "2025-10-08T19:47:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload-time = "2025-10-08T19:47:26.847Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload-time = "2025-10-08T19:47:27.961Z" }, + { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" }, + { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload-time = "2025-10-08T19:47:30.579Z" }, + { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" }, + { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" }, + { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" }, + { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" }, + { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" }, + { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" }, + { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" }, + { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" }, + { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" }, + { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" }, + { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" }, + { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" }, + { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" }, + { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" }, + { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" }, + { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" }, + { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" }, + { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" }, + { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" }, + { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" }, + { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" }, + { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" }, + { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" }, + { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" }, + { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, +] + +[[package]] +name = "protobuf" +version = "6.33.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, + { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, + { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, + { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, + { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, +] + +[[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 = "9.0.2" +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/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + +[[package]] +name = "pytest-httpx" +version = "0.36.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/5574834da9499066fa1a5ea9c336f94dba2eae02298d36dab192fcf95c86/pytest_httpx-0.36.0.tar.gz", hash = "sha256:9edb66a5fd4388ce3c343189bc67e7e1cb50b07c2e3fc83b97d511975e8a831b", size = 56793, upload-time = "2025-12-02T16:34:57.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/d2/1eb1ea9c84f0d2033eb0b49675afdc71aa4ea801b74615f00f3c33b725e3/pytest_httpx-0.36.0-py3-none-any.whl", hash = "sha256:bd4c120bb80e142df856e825ec9f17981effb84d159f9fa29ed97e2357c3a9c8", size = 20229, upload-time = "2025-12-02T16:34:56.45Z" }, +] + +[[package]] +name = "pytest-mock" +version = "3.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036, upload-time = "2025-09-16T16:37:27.081Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, +] + +[[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.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "rich" +version = "14.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/99/a4cab2acbb884f80e558b0771e97e21e939c5dfb460f488d19df485e8298/rich-14.3.2.tar.gz", hash = "sha256:e712f11c1a562a11843306f5ed999475f09ac31ffb64281f73ab29ffdda8b3b8", size = 230143, upload-time = "2026-02-01T16:20:47.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/45/615f5babd880b4bd7d405cc0dc348234c5ffb6ed1ea33e152ede08b2072d/rich-14.3.2-py3-none-any.whl", hash = "sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69", size = 309963, upload-time = "2026-02-01T16:20:46.078Z" }, +] + +[[package]] +name = "roman-numerals" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/dc/4e6ac71b511b141cf626357a3946679abeba4cf67bc7cc5a17920f31e10d/ruff-0.15.1.tar.gz", hash = "sha256:c590fe13fb57c97141ae975c03a1aedb3d3156030cabd740d6ff0b0d601e203f", size = 4540855, upload-time = "2026-02-12T23:09:09.998Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/bf/e6e4324238c17f9d9120a9d60aa99a7daaa21204c07fcd84e2ef03bb5fd1/ruff-0.15.1-py3-none-linux_armv6l.whl", hash = "sha256:b101ed7cf4615bda6ffe65bdb59f964e9f4a0d3f85cbf0e54f0ab76d7b90228a", size = 10367819, upload-time = "2026-02-12T23:09:03.598Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ea/c8f89d32e7912269d38c58f3649e453ac32c528f93bb7f4219258be2e7ed/ruff-0.15.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:939c995e9277e63ea632cc8d3fae17aa758526f49a9a850d2e7e758bfef46602", size = 10798618, upload-time = "2026-02-12T23:09:22.928Z" }, + { url = "https://files.pythonhosted.org/packages/5e/0f/1d0d88bc862624247d82c20c10d4c0f6bb2f346559d8af281674cf327f15/ruff-0.15.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1d83466455fdefe60b8d9c8df81d3c1bbb2115cede53549d3b522ce2bc703899", size = 10148518, upload-time = "2026-02-12T23:08:58.339Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c8/291c49cefaa4a9248e986256df2ade7add79388fe179e0691be06fae6f37/ruff-0.15.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9457e3c3291024866222b96108ab2d8265b477e5b1534c7ddb1810904858d16", size = 10518811, upload-time = "2026-02-12T23:09:31.865Z" }, + { url = "https://files.pythonhosted.org/packages/c3/1a/f5707440e5ae43ffa5365cac8bbb91e9665f4a883f560893829cf16a606b/ruff-0.15.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92c92b003e9d4f7fbd33b1867bb15a1b785b1735069108dfc23821ba045b29bc", size = 10196169, upload-time = "2026-02-12T23:09:17.306Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ff/26ddc8c4da04c8fd3ee65a89c9fb99eaa5c30394269d424461467be2271f/ruff-0.15.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fe5c41ab43e3a06778844c586251eb5a510f67125427625f9eb2b9526535779", size = 10990491, upload-time = "2026-02-12T23:09:25.503Z" }, + { url = "https://files.pythonhosted.org/packages/fc/00/50920cb385b89413f7cdb4bb9bc8fc59c1b0f30028d8bccc294189a54955/ruff-0.15.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66a6dd6df4d80dc382c6484f8ce1bcceb55c32e9f27a8b94c32f6c7331bf14fb", size = 11843280, upload-time = "2026-02-12T23:09:19.88Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6d/2f5cad8380caf5632a15460c323ae326f1e1a2b5b90a6ee7519017a017ca/ruff-0.15.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a4a42cbb8af0bda9bcd7606b064d7c0bc311a88d141d02f78920be6acb5aa83", size = 11274336, upload-time = "2026-02-12T23:09:14.907Z" }, + { url = "https://files.pythonhosted.org/packages/a3/1d/5f56cae1d6c40b8a318513599b35ea4b075d7dc1cd1d04449578c29d1d75/ruff-0.15.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ab064052c31dddada35079901592dfba2e05f5b1e43af3954aafcbc1096a5b2", size = 11137288, upload-time = "2026-02-12T23:09:07.475Z" }, + { url = "https://files.pythonhosted.org/packages/cd/20/6f8d7d8f768c93b0382b33b9306b3b999918816da46537d5a61635514635/ruff-0.15.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5631c940fe9fe91f817a4c2ea4e81f47bee3ca4aa646134a24374f3c19ad9454", size = 11070681, upload-time = "2026-02-12T23:08:55.43Z" }, + { url = "https://files.pythonhosted.org/packages/9a/67/d640ac76069f64cdea59dba02af2e00b1fa30e2103c7f8d049c0cff4cafd/ruff-0.15.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:68138a4ba184b4691ccdc39f7795c66b3c68160c586519e7e8444cf5a53e1b4c", size = 10486401, upload-time = "2026-02-12T23:09:27.927Z" }, + { url = "https://files.pythonhosted.org/packages/65/3d/e1429f64a3ff89297497916b88c32a5cc88eeca7e9c787072d0e7f1d3e1e/ruff-0.15.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:518f9af03bfc33c03bdb4cb63fabc935341bb7f54af500f92ac309ecfbba6330", size = 10197452, upload-time = "2026-02-12T23:09:12.147Z" }, + { url = "https://files.pythonhosted.org/packages/78/83/e2c3bade17dad63bf1e1c2ffaf11490603b760be149e1419b07049b36ef2/ruff-0.15.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:da79f4d6a826caaea95de0237a67e33b81e6ec2e25fc7e1993a4015dffca7c61", size = 10693900, upload-time = "2026-02-12T23:09:34.418Z" }, + { url = "https://files.pythonhosted.org/packages/a1/27/fdc0e11a813e6338e0706e8b39bb7a1d61ea5b36873b351acee7e524a72a/ruff-0.15.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3dd86dccb83cd7d4dcfac303ffc277e6048600dfc22e38158afa208e8bf94a1f", size = 11227302, upload-time = "2026-02-12T23:09:36.536Z" }, + { url = "https://files.pythonhosted.org/packages/f6/58/ac864a75067dcbd3b95be5ab4eb2b601d7fbc3d3d736a27e391a4f92a5c1/ruff-0.15.1-py3-none-win32.whl", hash = "sha256:660975d9cb49b5d5278b12b03bb9951d554543a90b74ed5d366b20e2c57c2098", size = 10462555, upload-time = "2026-02-12T23:09:29.899Z" }, + { url = "https://files.pythonhosted.org/packages/e0/5e/d4ccc8a27ecdb78116feac4935dfc39d1304536f4296168f91ed3ec00cd2/ruff-0.15.1-py3-none-win_amd64.whl", hash = "sha256:c820fef9dd5d4172a6570e5721704a96c6679b80cf7be41659ed439653f62336", size = 11599956, upload-time = "2026-02-12T23:09:01.157Z" }, + { url = "https://files.pythonhosted.org/packages/2a/07/5bda6a85b220c64c65686bc85bd0bbb23b29c62b3a9f9433fa55f17cda93/ruff-0.15.1-py3-none-win_arm64.whl", hash = "sha256:5ff7d5f0f88567850f45081fac8f4ec212be8d0b963e385c3f7d0d2eb4899416", size = 10874604, upload-time = "2026-02-12T23:09:05.515Z" }, +] + +[[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", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, 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 = "9.0.4" +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", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, 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", 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/42/50/a8c6ccc36d5eacdfd7913ddccd15a9cee03ecafc5ee2bc40e1f168d85022/sphinx-9.0.4.tar.gz", hash = "sha256:594ef59d042972abbc581d8baa577404abe4e6c3b04ef61bd7fc2acbd51f3fa3", size = 8710502, upload-time = "2025-12-04T07:45:27.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/3f/4bbd76424c393caead2e1eb89777f575dee5c8653e2d4b6afd7a564f5974/sphinx-9.0.4-py3-none-any.whl", hash = "sha256:5bebc595a5e943ea248b99c13814c1c5e10b3ece718976824ffa7959ff95fffb", size = 3917713, upload-time = "2025-12-04T07:45:24.944Z" }, +] + +[[package]] +name = "sphinx" +version = "9.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version >= '3.12'" }, + { name = "babel", marker = "python_full_version >= '3.12'" }, + { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "imagesize", marker = "python_full_version >= '3.12'" }, + { name = "jinja2", marker = "python_full_version >= '3.12'" }, + { name = "packaging", marker = "python_full_version >= '3.12'" }, + { name = "pygments", marker = "python_full_version >= '3.12'" }, + { name = "requests", marker = "python_full_version >= '3.12'" }, + { name = "roman-numerals", marker = "python_full_version >= '3.12'" }, + { name = "snowballstemmer", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, +] + +[[package]] +name = "sphinx-rtd-theme" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "sphinxcontrib-jquery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/84/68/a1bfbf38c0f7bccc9b10bbf76b94606f64acb1552ae394f0b8285bfaea25/sphinx_rtd_theme-3.1.0.tar.gz", hash = "sha256:b44276f2c276e909239a4f6c955aa667aaafeb78597923b1c60babc76db78e4c", size = 7620915, upload-time = "2026-01-12T16:03:31.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/c7/b5c8015d823bfda1a346adb2c634a2101d50bb75d421eb6dcb31acd25ebc/sphinx_rtd_theme-3.1.0-py2.py3-none-any.whl", hash = "sha256:1785824ae8e6632060490f67cf3a72d404a85d2d9fc26bce3619944de5682b89", size = 7655617, upload-time = "2026-01-12T16:03:28.101Z" }, +] + +[[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 = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +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.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, + { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, + { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, + { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, + { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, + { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, + { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, + { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, + { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, + { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, + { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, + { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, + { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, + { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, + { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, + { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, + { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, + { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, + { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, + { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, + { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, + { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, + { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, + { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, + { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, + { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, + { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, + { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, + { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, + { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, + { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] + +[[package]] +name = "wrapt" +version = "1.17.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/23/bb82321b86411eb51e5a5db3fb8f8032fd30bd7c2d74bfe936136b2fa1d6/wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04", size = 53482, upload-time = "2025-08-12T05:51:44.467Z" }, + { url = "https://files.pythonhosted.org/packages/45/69/f3c47642b79485a30a59c63f6d739ed779fb4cc8323205d047d741d55220/wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2", size = 38676, upload-time = "2025-08-12T05:51:32.636Z" }, + { url = "https://files.pythonhosted.org/packages/d1/71/e7e7f5670c1eafd9e990438e69d8fb46fa91a50785332e06b560c869454f/wrapt-1.17.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd341868a4b6714a5962c1af0bd44f7c404ef78720c7de4892901e540417111c", size = 38957, upload-time = "2025-08-12T05:51:54.655Z" }, + { url = "https://files.pythonhosted.org/packages/de/17/9f8f86755c191d6779d7ddead1a53c7a8aa18bccb7cea8e7e72dfa6a8a09/wrapt-1.17.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775", size = 81975, upload-time = "2025-08-12T05:52:30.109Z" }, + { url = "https://files.pythonhosted.org/packages/f2/15/dd576273491f9f43dd09fce517f6c2ce6eb4fe21681726068db0d0467096/wrapt-1.17.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:343e44b2a8e60e06a7e0d29c1671a0d9951f59174f3709962b5143f60a2a98bd", size = 83149, upload-time = "2025-08-12T05:52:09.316Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c4/5eb4ce0d4814521fee7aa806264bf7a114e748ad05110441cd5b8a5c744b/wrapt-1.17.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33486899acd2d7d3066156b03465b949da3fd41a5da6e394ec49d271baefcf05", size = 82209, upload-time = "2025-08-12T05:52:10.331Z" }, + { url = "https://files.pythonhosted.org/packages/31/4b/819e9e0eb5c8dc86f60dfc42aa4e2c0d6c3db8732bce93cc752e604bb5f5/wrapt-1.17.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e6f40a8aa5a92f150bdb3e1c44b7e98fb7113955b2e5394122fa5532fec4b418", size = 81551, upload-time = "2025-08-12T05:52:31.137Z" }, + { url = "https://files.pythonhosted.org/packages/f8/83/ed6baf89ba3a56694700139698cf703aac9f0f9eb03dab92f57551bd5385/wrapt-1.17.3-cp310-cp310-win32.whl", hash = "sha256:a36692b8491d30a8c75f1dfee65bef119d6f39ea84ee04d9f9311f83c5ad9390", size = 36464, upload-time = "2025-08-12T05:53:01.204Z" }, + { url = "https://files.pythonhosted.org/packages/2f/90/ee61d36862340ad7e9d15a02529df6b948676b9a5829fd5e16640156627d/wrapt-1.17.3-cp310-cp310-win_amd64.whl", hash = "sha256:afd964fd43b10c12213574db492cb8f73b2f0826c8df07a68288f8f19af2ebe6", size = 38748, upload-time = "2025-08-12T05:53:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/bd/c3/cefe0bd330d389c9983ced15d326f45373f4073c9f4a8c2f99b50bfea329/wrapt-1.17.3-cp310-cp310-win_arm64.whl", hash = "sha256:af338aa93554be859173c39c85243970dc6a289fa907402289eeae7543e1ae18", size = 36810, upload-time = "2025-08-12T05:52:51.906Z" }, + { url = "https://files.pythonhosted.org/packages/52/db/00e2a219213856074a213503fdac0511203dceefff26e1daa15250cc01a0/wrapt-1.17.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7", size = 53482, upload-time = "2025-08-12T05:51:45.79Z" }, + { url = "https://files.pythonhosted.org/packages/5e/30/ca3c4a5eba478408572096fe9ce36e6e915994dd26a4e9e98b4f729c06d9/wrapt-1.17.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85", size = 38674, upload-time = "2025-08-12T05:51:34.629Z" }, + { url = "https://files.pythonhosted.org/packages/31/25/3e8cc2c46b5329c5957cec959cb76a10718e1a513309c31399a4dad07eb3/wrapt-1.17.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f", size = 38959, upload-time = "2025-08-12T05:51:56.074Z" }, + { url = "https://files.pythonhosted.org/packages/5d/8f/a32a99fc03e4b37e31b57cb9cefc65050ea08147a8ce12f288616b05ef54/wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311", size = 82376, upload-time = "2025-08-12T05:52:32.134Z" }, + { url = "https://files.pythonhosted.org/packages/31/57/4930cb8d9d70d59c27ee1332a318c20291749b4fba31f113c2f8ac49a72e/wrapt-1.17.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1", size = 83604, upload-time = "2025-08-12T05:52:11.663Z" }, + { url = "https://files.pythonhosted.org/packages/a8/f3/1afd48de81d63dd66e01b263a6fbb86e1b5053b419b9b33d13e1f6d0f7d0/wrapt-1.17.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5", size = 82782, upload-time = "2025-08-12T05:52:12.626Z" }, + { url = "https://files.pythonhosted.org/packages/1e/d7/4ad5327612173b144998232f98a85bb24b60c352afb73bc48e3e0d2bdc4e/wrapt-1.17.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2", size = 82076, upload-time = "2025-08-12T05:52:33.168Z" }, + { url = "https://files.pythonhosted.org/packages/bb/59/e0adfc831674a65694f18ea6dc821f9fcb9ec82c2ce7e3d73a88ba2e8718/wrapt-1.17.3-cp311-cp311-win32.whl", hash = "sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89", size = 36457, upload-time = "2025-08-12T05:53:03.936Z" }, + { url = "https://files.pythonhosted.org/packages/83/88/16b7231ba49861b6f75fc309b11012ede4d6b0a9c90969d9e0db8d991aeb/wrapt-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77", size = 38745, upload-time = "2025-08-12T05:53:02.885Z" }, + { url = "https://files.pythonhosted.org/packages/9a/1e/c4d4f3398ec073012c51d1c8d87f715f56765444e1a4b11e5180577b7e6e/wrapt-1.17.3-cp311-cp311-win_arm64.whl", hash = "sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a", size = 36806, upload-time = "2025-08-12T05:52:53.368Z" }, + { url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998, upload-time = "2025-08-12T05:51:47.138Z" }, + { url = "https://files.pythonhosted.org/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba", size = 39020, upload-time = "2025-08-12T05:51:35.906Z" }, + { url = "https://files.pythonhosted.org/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd", size = 39098, upload-time = "2025-08-12T05:51:57.474Z" }, + { url = "https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828", size = 88036, upload-time = "2025-08-12T05:52:34.784Z" }, + { url = "https://files.pythonhosted.org/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9", size = 88156, upload-time = "2025-08-12T05:52:13.599Z" }, + { url = "https://files.pythonhosted.org/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396", size = 87102, upload-time = "2025-08-12T05:52:14.56Z" }, + { url = "https://files.pythonhosted.org/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc", size = 87732, upload-time = "2025-08-12T05:52:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe", size = 36705, upload-time = "2025-08-12T05:53:07.123Z" }, + { url = "https://files.pythonhosted.org/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c", size = 38877, upload-time = "2025-08-12T05:53:05.436Z" }, + { url = "https://files.pythonhosted.org/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6", size = 36885, upload-time = "2025-08-12T05:52:54.367Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, + { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, + { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, + { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, + { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, + { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" }, + { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" }, + { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" }, + { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" }, + { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" }, + { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" }, + { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" }, + { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" }, + { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" }, + { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" }, + { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" }, + { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" }, + { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" }, + { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" }, + { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" }, + { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, +] + +[[package]] +name = "yarl" +version = "1.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/43/a2204825342f37c337f5edb6637040fa14e365b2fcc2346960201d457579/yarl-1.22.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c7bd6683587567e5a49ee6e336e0612bec8329be1b7d4c8af5687dcdeb67ee1e", size = 140517, upload-time = "2025-10-06T14:08:42.494Z" }, + { url = "https://files.pythonhosted.org/packages/44/6f/674f3e6f02266428c56f704cd2501c22f78e8b2eeb23f153117cc86fb28a/yarl-1.22.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5cdac20da754f3a723cceea5b3448e1a2074866406adeb4ef35b469d089adb8f", size = 93495, upload-time = "2025-10-06T14:08:46.2Z" }, + { url = "https://files.pythonhosted.org/packages/b8/12/5b274d8a0f30c07b91b2f02cba69152600b47830fcfb465c108880fcee9c/yarl-1.22.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07a524d84df0c10f41e3ee918846e1974aba4ec017f990dc735aad487a0bdfdf", size = 94400, upload-time = "2025-10-06T14:08:47.855Z" }, + { url = "https://files.pythonhosted.org/packages/e2/7f/df1b6949b1fa1aa9ff6de6e2631876ad4b73c4437822026e85d8acb56bb1/yarl-1.22.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1b329cb8146d7b736677a2440e422eadd775d1806a81db2d4cded80a48efc1a", size = 347545, upload-time = "2025-10-06T14:08:49.683Z" }, + { url = "https://files.pythonhosted.org/packages/84/09/f92ed93bd6cd77872ab6c3462df45ca45cd058d8f1d0c9b4f54c1704429f/yarl-1.22.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:75976c6945d85dbb9ee6308cd7ff7b1fb9409380c82d6119bd778d8fcfe2931c", size = 319598, upload-time = "2025-10-06T14:08:51.215Z" }, + { url = "https://files.pythonhosted.org/packages/c3/97/ac3f3feae7d522cf7ccec3d340bb0b2b61c56cb9767923df62a135092c6b/yarl-1.22.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:80ddf7a5f8c86cb3eb4bc9028b07bbbf1f08a96c5c0bc1244be5e8fefcb94147", size = 363893, upload-time = "2025-10-06T14:08:53.144Z" }, + { url = "https://files.pythonhosted.org/packages/06/49/f3219097403b9c84a4d079b1d7bda62dd9b86d0d6e4428c02d46ab2c77fc/yarl-1.22.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d332fc2e3c94dad927f2112395772a4e4fedbcf8f80efc21ed7cdfae4d574fdb", size = 371240, upload-time = "2025-10-06T14:08:55.036Z" }, + { url = "https://files.pythonhosted.org/packages/35/9f/06b765d45c0e44e8ecf0fe15c9eacbbde342bb5b7561c46944f107bfb6c3/yarl-1.22.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cf71bf877efeac18b38d3930594c0948c82b64547c1cf420ba48722fe5509f6", size = 346965, upload-time = "2025-10-06T14:08:56.722Z" }, + { url = "https://files.pythonhosted.org/packages/c5/69/599e7cea8d0fcb1694323b0db0dda317fa3162f7b90166faddecf532166f/yarl-1.22.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:663e1cadaddae26be034a6ab6072449a8426ddb03d500f43daf952b74553bba0", size = 342026, upload-time = "2025-10-06T14:08:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/95/6f/9dfd12c8bc90fea9eab39832ee32ea48f8e53d1256252a77b710c065c89f/yarl-1.22.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6dcbb0829c671f305be48a7227918cfcd11276c2d637a8033a99a02b67bf9eda", size = 335637, upload-time = "2025-10-06T14:09:00.506Z" }, + { url = "https://files.pythonhosted.org/packages/57/2e/34c5b4eb9b07e16e873db5b182c71e5f06f9b5af388cdaa97736d79dd9a6/yarl-1.22.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f0d97c18dfd9a9af4490631905a3f131a8e4c9e80a39353919e2cfed8f00aedc", size = 359082, upload-time = "2025-10-06T14:09:01.936Z" }, + { url = "https://files.pythonhosted.org/packages/31/71/fa7e10fb772d273aa1f096ecb8ab8594117822f683bab7d2c5a89914c92a/yarl-1.22.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:437840083abe022c978470b942ff832c3940b2ad3734d424b7eaffcd07f76737", size = 357811, upload-time = "2025-10-06T14:09:03.445Z" }, + { url = "https://files.pythonhosted.org/packages/26/da/11374c04e8e1184a6a03cf9c8f5688d3e5cec83ed6f31ad3481b3207f709/yarl-1.22.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a899cbd98dce6f5d8de1aad31cb712ec0a530abc0a86bd6edaa47c1090138467", size = 351223, upload-time = "2025-10-06T14:09:05.401Z" }, + { url = "https://files.pythonhosted.org/packages/82/8f/e2d01f161b0c034a30410e375e191a5d27608c1f8693bab1a08b089ca096/yarl-1.22.0-cp310-cp310-win32.whl", hash = "sha256:595697f68bd1f0c1c159fcb97b661fc9c3f5db46498043555d04805430e79bea", size = 82118, upload-time = "2025-10-06T14:09:11.148Z" }, + { url = "https://files.pythonhosted.org/packages/62/46/94c76196642dbeae634c7a61ba3da88cd77bed875bf6e4a8bed037505aa6/yarl-1.22.0-cp310-cp310-win_amd64.whl", hash = "sha256:cb95a9b1adaa48e41815a55ae740cfda005758104049a640a398120bf02515ca", size = 86852, upload-time = "2025-10-06T14:09:12.958Z" }, + { url = "https://files.pythonhosted.org/packages/af/af/7df4f179d3b1a6dcb9a4bd2ffbc67642746fcafdb62580e66876ce83fff4/yarl-1.22.0-cp310-cp310-win_arm64.whl", hash = "sha256:b85b982afde6df99ecc996990d4ad7ccbdbb70e2a4ba4de0aecde5922ba98a0b", size = 82012, upload-time = "2025-10-06T14:09:14.664Z" }, + { url = "https://files.pythonhosted.org/packages/4d/27/5ab13fc84c76a0250afd3d26d5936349a35be56ce5785447d6c423b26d92/yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511", size = 141607, upload-time = "2025-10-06T14:09:16.298Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6", size = 94027, upload-time = "2025-10-06T14:09:17.786Z" }, + { url = "https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028", size = 94963, upload-time = "2025-10-06T14:09:19.662Z" }, + { url = "https://files.pythonhosted.org/packages/68/fe/2c1f674960c376e29cb0bec1249b117d11738db92a6ccc4a530b972648db/yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d", size = 368406, upload-time = "2025-10-06T14:09:21.402Z" }, + { url = "https://files.pythonhosted.org/packages/95/26/812a540e1c3c6418fec60e9bbd38e871eaba9545e94fa5eff8f4a8e28e1e/yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503", size = 336581, upload-time = "2025-10-06T14:09:22.98Z" }, + { url = "https://files.pythonhosted.org/packages/0b/f5/5777b19e26fdf98563985e481f8be3d8a39f8734147a6ebf459d0dab5a6b/yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65", size = 388924, upload-time = "2025-10-06T14:09:24.655Z" }, + { url = "https://files.pythonhosted.org/packages/86/08/24bd2477bd59c0bbd994fe1d93b126e0472e4e3df5a96a277b0a55309e89/yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e", size = 392890, upload-time = "2025-10-06T14:09:26.617Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d", size = 365819, upload-time = "2025-10-06T14:09:28.544Z" }, + { url = "https://files.pythonhosted.org/packages/30/2d/f715501cae832651d3282387c6a9236cd26bd00d0ff1e404b3dc52447884/yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7", size = 363601, upload-time = "2025-10-06T14:09:30.568Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f9/a678c992d78e394e7126ee0b0e4e71bd2775e4334d00a9278c06a6cce96a/yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967", size = 358072, upload-time = "2025-10-06T14:09:32.528Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d1/b49454411a60edb6fefdcad4f8e6dbba7d8019e3a508a1c5836cba6d0781/yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed", size = 385311, upload-time = "2025-10-06T14:09:34.634Z" }, + { url = "https://files.pythonhosted.org/packages/87/e5/40d7a94debb8448c7771a916d1861d6609dddf7958dc381117e7ba36d9e8/yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6", size = 381094, upload-time = "2025-10-06T14:09:36.268Z" }, + { url = "https://files.pythonhosted.org/packages/35/d8/611cc282502381ad855448643e1ad0538957fc82ae83dfe7762c14069e14/yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e", size = 370944, upload-time = "2025-10-06T14:09:37.872Z" }, + { url = "https://files.pythonhosted.org/packages/2d/df/fadd00fb1c90e1a5a8bd731fa3d3de2e165e5a3666a095b04e31b04d9cb6/yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca", size = 81804, upload-time = "2025-10-06T14:09:39.359Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b", size = 86858, upload-time = "2025-10-06T14:09:41.068Z" }, + { url = "https://files.pythonhosted.org/packages/2b/13/88b78b93ad3f2f0b78e13bfaaa24d11cbc746e93fe76d8c06bf139615646/yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376", size = 81637, upload-time = "2025-10-06T14:09:42.712Z" }, + { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, + { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" }, + { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" }, + { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" }, + { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" }, + { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" }, + { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" }, + { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" }, + { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" }, + { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" }, + { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" }, + { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f3/d67de7260456ee105dc1d162d43a019ecad6b91e2f51809d6cddaa56690e/yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53", size = 139980, upload-time = "2025-10-06T14:10:14.601Z" }, + { url = "https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a", size = 93424, upload-time = "2025-10-06T14:10:16.115Z" }, + { url = "https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c", size = 93821, upload-time = "2025-10-06T14:10:17.993Z" }, + { url = "https://files.pythonhosted.org/packages/61/3a/caf4e25036db0f2da4ca22a353dfeb3c9d3c95d2761ebe9b14df8fc16eb0/yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601", size = 373243, upload-time = "2025-10-06T14:10:19.44Z" }, + { url = "https://files.pythonhosted.org/packages/6e/9e/51a77ac7516e8e7803b06e01f74e78649c24ee1021eca3d6a739cb6ea49c/yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a", size = 342361, upload-time = "2025-10-06T14:10:21.124Z" }, + { url = "https://files.pythonhosted.org/packages/d4/f8/33b92454789dde8407f156c00303e9a891f1f51a0330b0fad7c909f87692/yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df", size = 387036, upload-time = "2025-10-06T14:10:22.902Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9a/c5db84ea024f76838220280f732970aa4ee154015d7f5c1bfb60a267af6f/yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2", size = 397671, upload-time = "2025-10-06T14:10:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b", size = 377059, upload-time = "2025-10-06T14:10:26.406Z" }, + { url = "https://files.pythonhosted.org/packages/a1/b9/ab437b261702ced75122ed78a876a6dec0a1b0f5e17a4ac7a9a2482d8abe/yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273", size = 365356, upload-time = "2025-10-06T14:10:28.461Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9d/8e1ae6d1d008a9567877b08f0ce4077a29974c04c062dabdb923ed98e6fe/yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a", size = 361331, upload-time = "2025-10-06T14:10:30.541Z" }, + { url = "https://files.pythonhosted.org/packages/ca/5a/09b7be3905962f145b73beb468cdd53db8aa171cf18c80400a54c5b82846/yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d", size = 382590, upload-time = "2025-10-06T14:10:33.352Z" }, + { url = "https://files.pythonhosted.org/packages/aa/7f/59ec509abf90eda5048b0bc3e2d7b5099dffdb3e6b127019895ab9d5ef44/yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02", size = 385316, upload-time = "2025-10-06T14:10:35.034Z" }, + { url = "https://files.pythonhosted.org/packages/e5/84/891158426bc8036bfdfd862fabd0e0fa25df4176ec793e447f4b85cf1be4/yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67", size = 374431, upload-time = "2025-10-06T14:10:37.76Z" }, + { url = "https://files.pythonhosted.org/packages/bb/49/03da1580665baa8bef5e8ed34c6df2c2aca0a2f28bf397ed238cc1bbc6f2/yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95", size = 81555, upload-time = "2025-10-06T14:10:39.649Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", size = 86965, upload-time = "2025-10-06T14:10:41.313Z" }, + { url = "https://files.pythonhosted.org/packages/98/4d/264a01eae03b6cf629ad69bae94e3b0e5344741e929073678e84bf7a3e3b/yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b", size = 81205, upload-time = "2025-10-06T14:10:43.167Z" }, + { url = "https://files.pythonhosted.org/packages/88/fc/6908f062a2f77b5f9f6d69cecb1747260831ff206adcbc5b510aff88df91/yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10", size = 146209, upload-time = "2025-10-06T14:10:44.643Z" }, + { url = "https://files.pythonhosted.org/packages/65/47/76594ae8eab26210b4867be6f49129861ad33da1f1ebdf7051e98492bf62/yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3", size = 95966, upload-time = "2025-10-06T14:10:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ce/05e9828a49271ba6b5b038b15b3934e996980dd78abdfeb52a04cfb9467e/yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9", size = 97312, upload-time = "2025-10-06T14:10:48.007Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c5/7dffad5e4f2265b29c9d7ec869c369e4223166e4f9206fc2243ee9eea727/yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f", size = 361967, upload-time = "2025-10-06T14:10:49.997Z" }, + { url = "https://files.pythonhosted.org/packages/50/b2/375b933c93a54bff7fc041e1a6ad2c0f6f733ffb0c6e642ce56ee3b39970/yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0", size = 323949, upload-time = "2025-10-06T14:10:52.004Z" }, + { url = "https://files.pythonhosted.org/packages/66/50/bfc2a29a1d78644c5a7220ce2f304f38248dc94124a326794e677634b6cf/yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e", size = 361818, upload-time = "2025-10-06T14:10:54.078Z" }, + { url = "https://files.pythonhosted.org/packages/46/96/f3941a46af7d5d0f0498f86d71275696800ddcdd20426298e572b19b91ff/yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708", size = 372626, upload-time = "2025-10-06T14:10:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/c1/42/8b27c83bb875cd89448e42cd627e0fb971fa1675c9ec546393d18826cb50/yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f", size = 341129, upload-time = "2025-10-06T14:10:57.985Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/99ca3122201b382a3cf7cc937b95235b0ac944f7e9f2d5331d50821ed352/yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d", size = 346776, upload-time = "2025-10-06T14:10:59.633Z" }, + { url = "https://files.pythonhosted.org/packages/85/b4/47328bf996acd01a4c16ef9dcd2f59c969f495073616586f78cd5f2efb99/yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8", size = 334879, upload-time = "2025-10-06T14:11:01.454Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ad/b77d7b3f14a4283bffb8e92c6026496f6de49751c2f97d4352242bba3990/yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5", size = 350996, upload-time = "2025-10-06T14:11:03.452Z" }, + { url = "https://files.pythonhosted.org/packages/81/c8/06e1d69295792ba54d556f06686cbd6a7ce39c22307100e3fb4a2c0b0a1d/yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f", size = 356047, upload-time = "2025-10-06T14:11:05.115Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b8/4c0e9e9f597074b208d18cef227d83aac36184bfbc6eab204ea55783dbc5/yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62", size = 342947, upload-time = "2025-10-06T14:11:08.137Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e5/11f140a58bf4c6ad7aca69a892bff0ee638c31bea4206748fc0df4ebcb3a/yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03", size = 86943, upload-time = "2025-10-06T14:11:10.284Z" }, + { url = "https://files.pythonhosted.org/packages/31/74/8b74bae38ed7fe6793d0c15a0c8207bbb819cf287788459e5ed230996cdd/yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249", size = 93715, upload-time = "2025-10-06T14:11:11.739Z" }, + { url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857, upload-time = "2025-10-06T14:11:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/46/b3/e20ef504049f1a1c54a814b4b9bed96d1ac0e0610c3b4da178f87209db05/yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4", size = 140520, upload-time = "2025-10-06T14:11:15.465Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/3532d990fdbab02e5ede063676b5c4260e7f3abea2151099c2aa745acc4c/yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683", size = 93504, upload-time = "2025-10-06T14:11:17.106Z" }, + { url = "https://files.pythonhosted.org/packages/11/63/ff458113c5c2dac9a9719ac68ee7c947cb621432bcf28c9972b1c0e83938/yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b", size = 94282, upload-time = "2025-10-06T14:11:19.064Z" }, + { url = "https://files.pythonhosted.org/packages/a7/bc/315a56aca762d44a6aaaf7ad253f04d996cb6b27bad34410f82d76ea8038/yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e", size = 372080, upload-time = "2025-10-06T14:11:20.996Z" }, + { url = "https://files.pythonhosted.org/packages/3f/3f/08e9b826ec2e099ea6e7c69a61272f4f6da62cb5b1b63590bb80ca2e4a40/yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590", size = 338696, upload-time = "2025-10-06T14:11:22.847Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9f/90360108e3b32bd76789088e99538febfea24a102380ae73827f62073543/yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2", size = 387121, upload-time = "2025-10-06T14:11:24.889Z" }, + { url = "https://files.pythonhosted.org/packages/98/92/ab8d4657bd5b46a38094cfaea498f18bb70ce6b63508fd7e909bd1f93066/yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da", size = 394080, upload-time = "2025-10-06T14:11:27.307Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e7/d8c5a7752fef68205296201f8ec2bf718f5c805a7a7e9880576c67600658/yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784", size = 372661, upload-time = "2025-10-06T14:11:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2e/f4d26183c8db0bb82d491b072f3127fb8c381a6206a3a56332714b79b751/yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b", size = 364645, upload-time = "2025-10-06T14:11:31.423Z" }, + { url = "https://files.pythonhosted.org/packages/80/7c/428e5812e6b87cd00ee8e898328a62c95825bf37c7fa87f0b6bb2ad31304/yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694", size = 355361, upload-time = "2025-10-06T14:11:33.055Z" }, + { url = "https://files.pythonhosted.org/packages/ec/2a/249405fd26776f8b13c067378ef4d7dd49c9098d1b6457cdd152a99e96a9/yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d", size = 381451, upload-time = "2025-10-06T14:11:35.136Z" }, + { url = "https://files.pythonhosted.org/packages/67/a8/fb6b1adbe98cf1e2dd9fad71003d3a63a1bc22459c6e15f5714eb9323b93/yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd", size = 383814, upload-time = "2025-10-06T14:11:37.094Z" }, + { url = "https://files.pythonhosted.org/packages/d9/f9/3aa2c0e480fb73e872ae2814c43bc1e734740bb0d54e8cb2a95925f98131/yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da", size = 370799, upload-time = "2025-10-06T14:11:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/50/3c/af9dba3b8b5eeb302f36f16f92791f3ea62e3f47763406abf6d5a4a3333b/yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2", size = 82990, upload-time = "2025-10-06T14:11:40.624Z" }, + { url = "https://files.pythonhosted.org/packages/ac/30/ac3a0c5bdc1d6efd1b41fa24d4897a4329b3b1e98de9449679dd327af4f0/yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79", size = 88292, upload-time = "2025-10-06T14:11:42.578Z" }, + { url = "https://files.pythonhosted.org/packages/df/0a/227ab4ff5b998a1b7410abc7b46c9b7a26b0ca9e86c34ba4b8d8bc7c63d5/yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33", size = 82888, upload-time = "2025-10-06T14:11:44.863Z" }, + { url = "https://files.pythonhosted.org/packages/06/5e/a15eb13db90abd87dfbefb9760c0f3f257ac42a5cac7e75dbc23bed97a9f/yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1", size = 146223, upload-time = "2025-10-06T14:11:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/18/82/9665c61910d4d84f41a5bf6837597c89e665fa88aa4941080704645932a9/yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca", size = 95981, upload-time = "2025-10-06T14:11:48.845Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9a/2f65743589809af4d0a6d3aa749343c4b5f4c380cc24a8e94a3c6625a808/yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53", size = 97303, upload-time = "2025-10-06T14:11:50.897Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ab/5b13d3e157505c43c3b43b5a776cbf7b24a02bc4cccc40314771197e3508/yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c", size = 361820, upload-time = "2025-10-06T14:11:52.549Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/242a5ef4677615cf95330cfc1b4610e78184400699bdda0acb897ef5e49a/yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf", size = 323203, upload-time = "2025-10-06T14:11:54.225Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/475509110d3f0153b43d06164cf4195c64d16999e0c7e2d8a099adcd6907/yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face", size = 363173, upload-time = "2025-10-06T14:11:56.069Z" }, + { url = "https://files.pythonhosted.org/packages/c9/66/59db471aecfbd559a1fd48aedd954435558cd98c7d0da8b03cc6c140a32c/yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b", size = 373562, upload-time = "2025-10-06T14:11:58.783Z" }, + { url = "https://files.pythonhosted.org/packages/03/1f/c5d94abc91557384719da10ff166b916107c1b45e4d0423a88457071dd88/yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486", size = 339828, upload-time = "2025-10-06T14:12:00.686Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/aa6a143d3afba17b6465733681c70cf175af89f76ec8d9286e08437a7454/yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138", size = 347551, upload-time = "2025-10-06T14:12:02.628Z" }, + { url = "https://files.pythonhosted.org/packages/43/3c/45a2b6d80195959239a7b2a8810506d4eea5487dce61c2a3393e7fc3c52e/yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a", size = 334512, upload-time = "2025-10-06T14:12:04.871Z" }, + { url = "https://files.pythonhosted.org/packages/86/a0/c2ab48d74599c7c84cb104ebd799c5813de252bea0f360ffc29d270c2caa/yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529", size = 352400, upload-time = "2025-10-06T14:12:06.624Z" }, + { url = "https://files.pythonhosted.org/packages/32/75/f8919b2eafc929567d3d8411f72bdb1a2109c01caaab4ebfa5f8ffadc15b/yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093", size = 357140, upload-time = "2025-10-06T14:12:08.362Z" }, + { url = "https://files.pythonhosted.org/packages/cf/72/6a85bba382f22cf78add705d8c3731748397d986e197e53ecc7835e76de7/yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c", size = 341473, upload-time = "2025-10-06T14:12:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/35/18/55e6011f7c044dc80b98893060773cefcfdbf60dfefb8cb2f58b9bacbd83/yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e", size = 89056, upload-time = "2025-10-06T14:12:13.317Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/0f0dccb6e59a9e7f122c5afd43568b1d31b8ab7dda5f1b01fb5c7025c9a9/yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27", size = 96292, upload-time = "2025-10-06T14:12:15.398Z" }, + { url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", size = 85171, upload-time = "2025-10-06T14:12:16.935Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, +] + +[[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" }, +]