Open ben-schreiber opened 5 months ago
Here are two suggestions which came to mind:
The args
parameter could be renamed. Something like,
def invoke(self, invocation_args: List[str], **kwargs) -> dbtRunnerResult: ...
Although, this would be a breaking change and would need to be handled accordingly.
To add a dedicated name for this use-case (e.g. macro_args
or something like that) which then would be translated back to args
def invoke(self, args: List[str], **kwargs) -> dbtRunnerResult:
if 'macro_args' in kwargs:
kwargs['args'] = kwargs.pop('macro_args')
This too would have to be appropriately documented since it breaks from the kwargs
convention of this method.
Thanks for reporting this along with the root cause and some ideas @ben-schreiber !
Agreed that the cause is the naming conflict between the positional parameter args
in the invoke
method and the --args
CLI flag for the run-operation
subcommand.
Pass in strings for the --args
CLI flag like this:
runner.invoke(
["run-operation", "some_macro", "--args", "{'arg1': 'foo', 'arg2': 'goo'}", "--vars", "{'some_var': 'doog'}"]
)
args
parameter of invoke
to something unique (e.g., invocation_args
) accepting that it will break for anyone using the positional argument as a named parameter.macro_args
) accepting the complications in the code and user experience--vars
flag for run-operation
requires the workaround above or monkey patching the definition of invoke
.The first option of giving it a unique name like invocation_args
seems most attractive to me. I'm guessing that it would be uncommon for folks to use a named parameter within calls to invoke()
, and none of our code examples use the named parameter.
If we went this route, we would call it out in a migration guide. We'd also want to take care to choose a name that is unique enough that we'd be unlikely to ever add a CLI flag with the same name. See here for an example script that can get the names of all the CLI flags.
@ben-schreiber To help us understand the trade-offs here, could you share more about the effect on the user experience if we leave it as-is and users only have the workaround you mentioned?
@dbeatty10 I agree with you that option one is ideal. I would extend it even further to update the method's signature to include /
:
def invoke(self, invocation_args: List[str], /, **kwargs) -> dbtRunnerResult: ...
thereby formalizing the desire to separate the usage of invocation_args
(positional only) from kwargs
(keyword only) in code. This would also allow the invocation_args
argument to be be given a unique name without worrying as much about readability/usability for end users.
In regards to your question, the effect is not terribly large as long as this edge case is clearly documented. I encountered the issue when building a wrapper around DBT's programmatic invocations for Apache Airflow. In that use case, passing keyword arguments to invoke
is more natural than converting Pythonic objects to the CLI representation.
Also, here is another/shorter monkeypatch:
from typing import List
from dbt.cli.main import dbtRunnerResult, dbtRunner
invoke_v1 = dbtRunner.invoke
def invoke_v2(self, invocation_args: List[str], /, **kwargs) -> dbtRunnerResult:
args_copy = list(invocation_args)
kwargs_copy = dict(kwargs)
if 'args' in kwargs_copy:
args_copy += ['--args', kwargs_copy.pop('args')]
return invoke_v1(self, args_copy, **kwargs_copy)
dbtRunner.invoke = invoke_v2
Is this a new bug in dbt-core?
Current Behavior
Passing the
--args
parameter as a kwarg to thedbtRunner.invoke
method throws aTypeError
. This forces the parameter to only be pass as:This looks to be caused by a naming conflict with the
invoke
method hereExpected Behavior
To also be able to pass it as a kwarg
Steps To Reproduce
Relevant log output
Environment
Which database adapter are you using with dbt?
snowflake
Additional Context
No response