I want to pass a string to an op, it works with th...
# ask-community
c
I want to pass a string to an op, it works with this:
Copy code
@op()
def my_string():
    return "my_string"
Then I call it like this
Copy code
my_op(my_string(), other_var)
But that’s kind of a smell. I’d like to use this but it doesn’t work:
Copy code
my_op("my_string", other_var)
Is there a cleaner way?
🤖 1
c
Config schema doesn’t work in my case the string is not static I’m doing something like this:
Copy code
my_op("name", other_var)
my_op("age", other_var)
z
hmm sounds like you might want a Dynamic Graph then. with Dynamic Outputs you can iterate over and yield the strings you want your downstream op to operate on
c
That would be a bit overkill. I’m just trying to pass a string to an op
z
alright well there's also a values resource. or you could have the graph take an input and pass it to your op from there. but more generally you can't pass raw strings to ops in a graph definition - the options I've listed and the solution you came up with are some of the various ways to pass configuration and inputs to ops.
c
Would be even more a code smell to me 🙂 I guess I don’t have a choice to use a dumb op
z
just curious, why do you consider this a code smell?
to me hardcoding literal strings in a dag definition is quite a code smell
resources and external configuration are a way to decouple your graph structure from your configuration in order to make them more reusable and less reliant on external dependencies
c
Left is a lot of code that does nothing. Right is what makes sense but it doesn’t work
If you have a better way to do this. I’m all hear
a
Would it make sense to define a function that constructs ops?
Copy code
def my_op(subtype: str):
    @op(
        name=f"my_op_{subtype}"
    )
    def wrapped(context, value):
        """
        do something with value + subtype here
        """

    return wrapped


@graph
def my_graph():
    other_var = some_other_op()
    my_op("name")(other_var)
    my_op("age")(other_var)
c
That’s a solution but then I think that my initial solution is actually the most simple.
a
Sure -- I guess it depends on how many distinct string literals you need to pass in. For a large number of string literals, the "op factory" pattern will simplify things, but for a small number, adding explicit constant ops will be simpler.
c
I totally agree
z
here's the dynamic graph solution - this way if your list of strings you need to handle_data for changes or grows you don't have to change any code:
Copy code
@op(config_schema={"configurable_strings": [str]})
def get_config(context):
    for i, s in enumerate(context.op_config["configurable_strings"]):
        yield DynamicOutput(s, mapping_key=str(i))

@graph
def my_graph():
    data = get_data()
    get_config().map(lambda x: handle_data(x, data))
but whatever works best for you is the right solution
c
That’s actually a nice design.
I think I still prefer the simplicity of using dumb ops. But will definitely keep your pattern in mind
Thanks
🎉 1
s
another option would be:
Copy code
@op(config_schema=str)
def my_op(context, other_input):
    my_str = context.op_config

@graph
def my_graph():
    my_op.configured("age", name="age_my_op")(other_var)
    my_op.configured("name", name="name_my_op")(other_var)
z
ah nice one @sandy