Skip to content
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

[ENHANCEMENT] CLI init command implemented for v3 api #2626

Merged
merged 15 commits into from
Mar 31, 2021
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Develop
* [ENHANCEMENT] CLI `docs clean` command implemented for v3 api
* [MAINTENANCE] Add testing for overwrite_existing in sanitize_yaml_and_save_datasource #2613
* [ENHANCEMENT] DataContext.clean_data_docs now raises helpful errors
* [ENHANCEMENT] CLI `init` command implemented for v3 api

0.13.15
-----------------
Expand Down
34 changes: 19 additions & 15 deletions great_expectations/cli/cli_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
~ Always know what to expect from your data ~
</cyan>"""

LETS_BEGIN_PROMPT = """Let's configure a new Data Context.
LETS_BEGIN_PROMPT = """Let's create a new Data Context to hold your project configuration.

First, Great Expectations will create a new directory:
Great Expectations will create a new directory with the following structure:

great_expectations
|-- great_expectations.yml
Expand All @@ -33,9 +33,8 @@
"OK. You must run <green>great_expectations init</green> to fix the missing files!"
)

COMPLETE_ONBOARDING_PROMPT = """To run locally, we need some files that are not in source control.
- Anything existing will not be modified.
- Would you like to fix this automatically?"""
COMPLETE_ONBOARDING_PROMPT = """
It looks like you have a partially initialized Great Expectations project. Would you like to fix this automatically by adding the missing files (existing files will not be modified)?"""

SLACK_SETUP_INTRO = """
<cyan>========== Slack Notifications ==========</cyan>
Expand All @@ -60,17 +59,22 @@
- You may need to add secrets to `<yellow>great_expectations/uncommitted/config_variables.yml</yellow>` to finish onboarding.
"""

BUILD_DOCS_PROMPT = "Would you like to build & view this project's Data Docs!?"
READY_FOR_CUSTOMIZATION = """<cyan>Congratulations! You are now ready to customize your Great Expectations configuration.</cyan>"""

NO_DATASOURCES_FOUND = """<red>Error: No datasources were found.</red> Please add one by:
- running `<green>great_expectations datasource new</green>` or
- by editing the {} file
""".format(
DataContext.GE_YML
)
HOW_TO_CUSTOMIZE = f"""\n<cyan>You can customize your configuration in many ways. Here are some examples:</cyan>

SETUP_SUCCESS = "\n<cyan>Congratulations! Great Expectations is now set up.</cyan>"
<cyan>Use the CLI to:</cyan>
- Run `<green>great_expectations datasource new</green>` to connect to your data
- Run `<green>great_expectations checkpoint new <checkpoint_name></green>` to bundle data with Expectation Suite(s) in a Checkpoint definition for later re-validation
- Create, edit, list, profile Expectation Suites
- Manage and customize Data Docs, Stores

SECTION_SEPARATOR = "\n================================================================================\n"
<cyan>Edit your configuration in {DataContext.GE_YML} to:</cyan>
- Move Stores to the cloud
- Add Slack notifications, PagerDuty alerts, etc.
- Customize your Data Docs

DONE = "Done"
<cyan>Please see our documentation for more configuration options!</cyan>
"""

SECTION_SEPARATOR = "\n================================================================================\n"
142 changes: 30 additions & 112 deletions great_expectations/cli/init.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,29 @@
import os
import sys
import warnings

import click

from great_expectations import DataContext
from great_expectations import exceptions as ge_exceptions
from great_expectations.cli import toolkit
from great_expectations.cli.build_docs import build_docs
from great_expectations.cli.cli_messages import (
BUILD_DOCS_PROMPT,
COMPLETE_ONBOARDING_PROMPT,
GREETING,
HOW_TO_CUSTOMIZE,
LETS_BEGIN_PROMPT,
ONBOARDING_COMPLETE,
PROJECT_IS_COMPLETE,
READY_FOR_CUSTOMIZATION,
RUN_INIT_AGAIN,
SECTION_SEPARATOR,
SETUP_SUCCESS,
SLACK_LATER,
SLACK_SETUP_COMPLETE,
SLACK_SETUP_INTRO,
SLACK_SETUP_PROMPT,
SLACK_WEBHOOK_PROMPT,
)
from great_expectations.cli.pretty_printing import (
cli_message,
display_not_implemented_message_and_exit,
)
from great_expectations.cli.pretty_printing import cli_message
from great_expectations.exceptions import (
DataContextError,
DatasourceInitializationError,
Expand All @@ -41,19 +39,13 @@


@click.command()
@click.option(
# Note this --no-view option is mostly here for tests
"--view/--no-view",
help="By default open in browser unless you specify the --no-view flag.",
default=True,
)
Comment on lines -44 to -49
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YAY

@click.option(
"--usage-stats/--no-usage-stats",
help="By default, usage statistics are enabled unless you specify the --no-usage-stats flag.",
default=True,
)
@click.pass_context
def init(ctx, view, usage_stats):
def init(ctx, usage_stats):
"""
Initialize a new Great Expectations project.

Expand All @@ -63,7 +55,6 @@ def init(ctx, view, usage_stats):
It scaffolds directories, sets up notebooks, creates a project file, and
appends to a `.gitignore` file.
"""
display_not_implemented_message_and_exit()
directory = toolkit.parse_cli_config_file_location(
config_file_location=ctx.obj.config_file_location
).get("directory")
Expand All @@ -74,22 +65,34 @@ def init(ctx, view, usage_stats):
cli_message(GREETING)

if DataContext.does_config_exist_on_disk(ge_dir):
message = (
f"""Warning. An existing `{DataContext.GE_YML}` was found here: {ge_dir}."""
)
warnings.warn(message)
try:
if DataContext.is_project_initialized(ge_dir):
# Ensure the context can be instantiated
project_file_structure_exists = (
DataContext.does_config_exist_on_disk(ge_dir)
and DataContext.all_uncommitted_directories_exist(ge_dir)
and DataContext.config_variables_yml_exist(ge_dir)
)
if project_file_structure_exists:
cli_message(PROJECT_IS_COMPLETE)
sys.exit(0)
else:
# Prompt to modify the project to add missing files
if not ctx.obj.assume_yes:
if not click.confirm(COMPLETE_ONBOARDING_PROMPT, default=True):
cli_message(RUN_INIT_AGAIN)
exit(0)

except (DataContextError, DatasourceInitializationError) as e:
cli_message("<red>{}</red>".format(e.message))
sys.exit(1)

try:
context = DataContext.create(
target_directory, usage_statistics_enabled=usage_stats
)
DataContext.create(target_directory, usage_statistics_enabled=usage_stats)
cli_message(ONBOARDING_COMPLETE)
# TODO if this is correct, ensure this is covered by a test
# cli_message(SETUP_SUCCESS)
# exit(0)

except DataContextError as e:
cli_message("<red>{}</red>".format(e.message))
# TODO ensure this is covered by a test
Expand All @@ -98,7 +101,6 @@ def init(ctx, view, usage_stats):
if not ctx.obj.assume_yes:
if not click.confirm(LETS_BEGIN_PROMPT, default=True):
cli_message(RUN_INIT_AGAIN)
# TODO ensure this is covered by a test
exit(0)

try:
Expand All @@ -112,94 +114,10 @@ def init(ctx, view, usage_stats):
# TODO ensure this is covered by a test
cli_message("<red>{}</red>".format(e))

# Skip the rest of setup if --assume-yes flag is passed
if ctx.obj.assume_yes:
cli_message(SECTION_SEPARATOR)
cli_message(SETUP_SUCCESS)
sys.exit(0)

try:
# if expectations exist, offer to build docs
context = DataContext(ge_dir)
if context.list_expectation_suites():
if click.confirm(BUILD_DOCS_PROMPT, default=True):
build_docs(context, view=view)

else:
datasources = context.list_datasources()
if len(datasources) == 0:
cli_message(SECTION_SEPARATOR)
if not click.confirm(
"Would you like to configure a Datasource?", default=True
):
cli_message("Okay, bye!")
sys.exit(1)
datasource_name, data_source_type = add_datasource_impl(
context, choose_one_data_asset=False
)
if not datasource_name: # no datasource was created
sys.exit(1)

datasources = context.list_datasources()
if len(datasources) == 1:
datasource_name = datasources[0]["name"]

cli_message(SECTION_SEPARATOR)
if not click.confirm(
"Would you like to profile new Expectations for a single data asset within your new Datasource?",
default=True,
):
cli_message(
"Okay, exiting now. To learn more about Profilers, run great_expectations profile --help or visit docs.greatexpectations.io!"
)
sys.exit(1)

(
success,
suite_name,
profiling_results,
) = toolkit.create_expectation_suite(
context,
datasource_name=datasource_name,
additional_batch_kwargs={"limit": 1000},
flag_build_docs=False,
open_docs=False,
)

cli_message(SECTION_SEPARATOR)
if not click.confirm(
"Would you like to build Data Docs?", default=True
):
cli_message(
"Okay, exiting now. To learn more about Data Docs, run great_expectations docs --help or visit docs.greatexpectations.io!"
)
sys.exit(1)

build_docs(context, view=False)

if not click.confirm(
"\nWould you like to view your new Expectations in Data Docs? This will open a new browser window.",
default=True,
):
cli_message(
"Okay, exiting now. You can view the site that has been created in a browser, or visit docs.greatexpectations.io for more information!"
)
sys.exit(1)
toolkit.attempt_to_open_validation_results_in_data_docs(
context, profiling_results
)

cli_message(SECTION_SEPARATOR)
cli_message(SETUP_SUCCESS)
sys.exit(0)
except (
DataContextError,
ge_exceptions.ProfilerError,
OSError,
SQLAlchemyError,
) as e:
cli_message("<red>{}</red>".format(e))
sys.exit(1)
Comment on lines -115 to -202
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👋 Bye!

cli_message(SECTION_SEPARATOR)
cli_message(READY_FOR_CUSTOMIZATION)
cli_message(HOW_TO_CUSTOMIZE)
sys.exit(0)


def _slack_setup(context):
Expand Down