#Thread

1 messages ยท Page 1 of 1 (latest)

oak swallow
#

the signature of the funcion is

async def __build(self) -> dict[str, Container]

shrewd badger
#

That function isn't decorated with @function, is it?

#

What do your imports look like? And what's the file structure?

#

Did you keep the file layout from the new dagger init, or from what you had?

oak swallow
#

yes si not decorated since it is more like internal funcion, i dont really like to call it from outside. and yes i keep the file layout as from the init

shrewd badger
#

Can you share more details? It's hard to tell what you have going on.

oak swallow
#

sure this si an extract of the class i have


@object_type
class Btd:
...

    @function
    async def run(self) -> None:
        """Run the project."""

        build_promise = self.__build()
        tests_promise = self.__run_tests()

        [containers, tests_results] = await asyncio.gather(*[build_promise, tests_promise])

        ...
...

    async def __build(self) -> dict[str, Container]:
        """Build project container image."""

        print("Building project container images")

        x = (
            dag.container(platform=Platform("linux/amd64"))
            .build(context=self.working_dir.directory('x'),
                   dockerfile="Dockerfile")
        )

        y = (
            dag.container(platform=Platform("linux/amd64"))
            .build(context=self.working_dir.directory('y'),
                   dockerfile="Dockerfile")
        )

        containers = await asyncio.gather(x, y)

        print("Project container images built")

        return {
            "1": containers[0],
            "2": containers[1]
        }
...
#

my dagger.json

{
  "name": "btd",
  "engineVersion": "v0.18.4",
  "sdk": {
    "source": "python"
  }
}
#

my pyproject.toml

[project]
name = "btd"
description = "Build, Test and Deploy"
version = "1.1.0"
requires-python = ">=3.12"
dependencies = [
    "dagger-io>=0.18",
    "requests>=2.32",
    "botocore>=1.37",
    "boto3>=1.37",
]
[build-system]
requires = [
    "hatchling>=1.27"
]
build-backend = "hatchling.build"
[tool.uv.sources]
dagger-io = { path = "sdk", editable = true }
shrewd badger
#

What's the path of the file where your class is?

oak swallow
shrewd badger
#

It kind of looks like pyproject.toml and others are within btd/src instead of btd. Hard to say for sure from the image. Can you confirm?

oak swallow
#

no they are outside

shrewd badger
#

Can you confirm the imports in __init__.py and main.py?

oak swallow
#

init.py

"""A generated module for Btd functions

This module has been generated via dagger init and serves as a reference to
basic module structure as you get started with Dagger.

Two functions have been pre-created. You can modify, delete, or add to them,
as needed. They demonstrate usage of arguments and return types using simple
echo and grep commands. The functions can be called from the dagger CLI or
from one of the SDKs.

The first line in this comment block is a short description line and the
rest is a long description with more detail on the module's purpose or usage,
if appropriate. All modules should have a short description.
"""

from .main import Btd as Btd
#

the main.py is the class extract i share above

shrewd badger
oak swallow
#
import asyncio
import base64
import re
from typing import Annotated

import boto3
import requests
from dagger import dag, function, object_type, Directory, Doc, Container, Secret, Platform

@object_type
class Btd:
...

    @function
    async def run(self) -> None:
        """Run the project."""

        build_promise = self.__build()
        tests_promise = self.__run_tests()

        [containers, tests_results] = await asyncio.gather(*[build_promise, tests_promise])

        ...
...

    async def __build(self) -> dict[str, Container]:
        """Build project container image."""

        print("Building project container images")

        x = (
            dag.container(platform=Platform("linux/amd64"))
            .build(context=self.working_dir.directory('x'),
                   dockerfile="Dockerfile")
        )

        y = (
            dag.container(platform=Platform("linux/amd64"))
            .build(context=self.working_dir.directory('y'),
                   dockerfile="Dockerfile")
        )

        containers = await asyncio.gather(x, y)

        print("Project container images built")

        return {
            "1": containers[0],
            "2": containers[1]
        }
...

shrewd badger
#

The error you're getting... does it have a stack trace? Can you tell which line is throwing it?

oak swallow
#

let me run it on verbose mode

shrewd badger
#

Does it happen with dagger functions or only dagger call?

oak swallow
#

dagger call

shrewd badger
#

Doesn't happen with dagger functions?

oak swallow
#

never tried, whats the diff?

shrewd badger
#

Doesn't execute any function, just shows you what the module is reporting on available functions.

#

Can you try it?

oak swallow
#

dagger functions
โœ” connect 0.3s
โœ” load module 1.6s

Name Description
run Run the project.

shrewd badger
#

Ok, so it has to be something within the body of run. I assume you're doing dagger call run?

oak swallow
#

yes, with few arguments. but yes. and it fails when the run function calls the __build one which returns a container dict

shrewd badger
#

Ok, let me see if I can make a small repro.

#

Ok, I was able to repro.

#

I suspect it's from your use of asyncio. Give me a moment pls.

oak swallow
#

great, thanks for your help

shrewd badger
#

Can you tell which python version you had before?

oak swallow
#

0.17.2

#

and it was working fine till yesterday

shrewd badger
#

That's the dagger version, I mean the Python version that your module was using.

oak swallow
#

we run it on GHA with

      - name: Call Dagger Function
        uses: dagger/dagger-for-github@8.0.0
        env:
..
        with:
          workdir: ".github/ci/btd"
          version: "latest"
          args: ... run
shrewd badger
#

Smallest repro here:

import asyncio
from dagger import dag, function, object_type


@object_type
class Btd:
    @function
    async def run(self) -> None:
        x = dag.container()
        y = dag.container()
        containers = await asyncio.gather(x, y)

        # error: Unhashable type 'Container'
        containers[0]
oak swallow
#

i also test it here, same output, great we have a replicable bug. do you see something off or its something inside sdk it self?

shrewd badger
#

I would do it differently, without asyncio.gather(). It's not clear to me yet how you had this working before. Nothing I can think of from the SDK side.

#

Even smaller repro:

@object_type
class Btd:
    @function
    async def run(self):
        await asyncio.gather(dag.container())
#

And the traceback:

Traceback (most recent call last):
  File "/src/xxh3:5997e2ba730ab363/sdk/src/dagger/mod/_module.py", line 291, in get_structured_result
    result = await self.call(fn.wrapped, *bound.args, **bound.kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/src/xxh3:5997e2ba730ab363/sdk/src/dagger/mod/_module.py", line 320, in call
    return await await_maybe(func(*args, **kwargs))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/src/xxh3:5997e2ba730ab363/sdk/src/dagger/mod/_utils.py", line 37, in await_maybe
    return await value if inspect.iscoroutine(value) else cast(T, value)
           ^^^^^^^^^^^
  File "/src/xxh3:5997e2ba730ab363/src/btd/main.py", line 11, in run
    await asyncio.gather(dag.container())
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/asyncio/tasks.py", line 830, in gather
    if arg not in arg_to_fut:
       ^^^^^^^^^^^^^^^^^^^^^
TypeError: unhashable type: 'Container'
#
The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/src/xxh3:5997e2ba730ab363/sdk/src/dagger/mod/_module.py", line 93, in serve
    result = await self._invoke(parent_name)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/src/xxh3:5997e2ba730ab363/sdk/src/dagger/mod/_module.py", line 243, in _invoke
    result = await self.get_result(
             ^^^^^^^^^^^^^^^^^^^^^^
  File "/src/xxh3:5997e2ba730ab363/sdk/src/dagger/mod/_module.py", line 308, in get_result
    result, return_type = await self.get_structured_result(
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/src/xxh3:5997e2ba730ab363/sdk/src/dagger/mod/_module.py", line 293, in get_structured_result
    raise FunctionError(e) from e
dagger.mod._exceptions.FunctionError: unhashable type: 'Container'
#

@oak swallow, try adding .sync() to each Container object before passing to asyncio.gather.

#

Like

@object_type
class Btd:
    @function
    async def run(self):
        await self.__build()

    async def __build(self) -> dict[str, dagger.Container]:
        x = dag.container().sync()
        y = dag.container().sync()

        containers = await asyncio.gather(x, y)

        return {
            "1": containers[0],
            "2": containers[1],
        }
oak swallow
#

what will sync do?

shrewd badger
# oak swallow what will sync do?

It's not documented that you can actually do this:

await dag.container().from_("alpine")

The from_() function is lazy, but this works because the returned object (Container) implements Awaitable, making the above equivalent to:

await dag.container().from_("alpine").sync()

However, while sync() returns a coroutine, which is not exactly the same as Container implementing __await__, though it should behave the same.

Basically sync() forces evaluation of the container. Without it it's a lazy object.

#

The asyncio.gather function deals with a more lower level concept: "Futures". It represents an eventual result. It's better to use a task group instead, imo.

oak swallow
#

interesting

#

thanks for the help

shrewd badger
#

Np, still not clear why this worked for you before though ๐Ÿ™‚