-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Functional test framework working with Click, dbtRunner #6387
Conversation
Thank you for your pull request! We could not find a changelog entry for this change. For details on how to document a change, see the contributing guide. |
b608756
to
d37d023
Compare
3efaf94
to
a58b1fe
Compare
0d49f0c
to
e298039
Compare
core/dbt/cli/main.py
Outdated
ctx.obj["project"] = project | ||
|
||
# Context for downstream commands | ||
ctx.obj["flags"] = flags |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not saying we need to change it but since we actually construct flags, profile, project within the command, is ctx.obj
still the best place to put it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm, definitely worth questioning! An alternative could be to inject profile, project, manifest, etc into the command method's arg
s, similar to how click.pass_context
works. This way we'd be a little less coupled to click, although we'd probably still need to use the click context to store any project/profile overrides passed to dbtRunner
. There would be some naming conflicts to resolve (ex: params from options named --profile
and a profile
override).
I think it's worth keeping in mind alternatives to using ctx.obj
for passing user-configured objects to commands in mind as we go as the current approach doesn't preclude us from changing this up in the future.
|
||
|
||
# Programmatic invocation | ||
class dbtRunner: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this first rough version of thin wrapper, for end users who want to programmatically invoke dbt-core
via Python API? Thinking:
# initialize longer-lived dbtRunner
# by implicitly loading project/profile/etc based on config + cwd
dbt = dbtRunner()
# submit commands to dbtRunner
# each command returns
# results: List[Result]
# success: bool
results, success = dbt.run(...)
results, success = dbt.test(...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, exactly!
# initialize longer-lived dbtRunner
# by implicitly loading project/profile/etc based on config + cwd
dbt = dbtRunner()
^ We could load profile/project/etc from defaults at during dbtRunner
instantiation but depending on the args in subsequent commands, they may require reloading. Something to consider for a follow-up though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
results, success = dbt.run(...)
results, success = dbt.test(...)
this part is tbd I think? current way would be pass in command through args, the main advantage is some CLI args need to be specified before the actual command. So exposing things this way provide best flexibility at the moment.
args = ['run', '--project-dir', 'something']
res, success = dbt.invoke(args)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
current way would be pass in command through args
We'll definitely want to support this generic pattern ("given an arbitrary list of CLI arg strings, invoke"). I could also see us wanting to support a more structured approach, where users can build up a dictionary of attributes and pass it into a named command. All of which is to say, we should design this programmatic interface for real, before we commit to it as a public-facing API — and that means sitting down, writing some user stories, talking with community members. All good stuff to do next year :)
the main advantage is some CLI args need to be specified before the actual command
Lukewarm take: This is pretty confusing for end users! We should really seek to support flags in any order (and resolve any resulting ambiguities by actually renaming flags). I know we implemented the new CLI modeling in accordance with the old CLI model — parity is a good goal to shoot for with any refactor — but it would be great to just support both of these, seamlessly and equivalently:
$ dbt --no-use-color run --select one_model
$ dbt run --select one_model --no-use-color
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cc @iknox-fa
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, that wasn't part of the original plan, but I guess we can see about supporting arbitrary flag ordering-- It's not something we should take on in this PR though. @jtcohen6, can you write up a ticket that we can put into the estimation process.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2995132
to
5a1c687
Compare
# TODO: Decouple target from profile, and remove the need for profile here: | ||
# https://github.com/dbt-labs/dbt-core/issues/6257 | ||
if not ctx.obj.get("profile"): | ||
raise DbtProjectError("profile required for project") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a more appropriate exception to raise here? I'm not too concerned either way because we should be able to get away from requiring a profile at which point the exception won't be necessary here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we also check for requires.profile
is added to the current command or not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that may be overly-rigid. Technically this method only requires profile to exist in ctx.obj
, and shouldn't need to be concerned with how it gets there. Adding that check would make it unnecessarily difficult to change the 'how'. Also not sure it's worth the effort given we're looking to decouple profile from project creation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a more appropriate exception to raise here?
This seems appropriate to me. I do know that exceptions are getting some closer attention from @emmyoop and others soon so I wouldn't stress it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's also a DbtProfileError
Exception that is also a subclass of `DbtConfigError.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me. There is one test.py
file that seems to be empty that you can probably remove.
28406d9
to
c2a8ef6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me, also tried to extend this in another PR, works great!
{envpython} -m pytest --cov=core {posargs} tests/functional | ||
{envpython} -m pytest --cov=core {posargs} tests/adapter | ||
{envpython} -m pytest --cov=core -m profile_postgres {posargs} test/integration |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nothing important, but I'm curious why the test run-order changed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given the test/integration tests will continue to fail on this branch, I moved them to run last so that errors from tests/functional
would be visible in CI logs
@@ -73,7 +73,8 @@ def run_dbt(args: List[str] = None, expect_pass=True): | |||
args = ["run"] | |||
|
|||
print("\n\nInvoking dbt with {}".format(args)) | |||
res, success = handle_and_check(args) | |||
dbt = dbtRunner() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love how simple this change ended up being. It's a sign that we made the right abstractions when we implemented the new functional testing framework.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great @MichelleArk!
Ship it!
a102b23
to
2799bf2
Compare
resolves #6096, #6381
Description
dbtRunner
to run dbt CLI programmatically. Accepts profile, project, and manifest (for later), and uses them in combination with CLI args duringinvoke
calls to invoke the CLI.tests/functional
tests throughdbtRunner
tests/functional/dependencies/test_simple_dependency.py::TestSimpleDependencyUnpinned::test_simple_dependency
, which only depends on runningdeps
- and succeeds!test/integration
tests last so that we can start surfacing failing functional tests in CI logs.Checklist
changie new
to create a changelog entry