Skip to content

Commit 63b613b

Browse files
committed
[dg] Make init command work with a dirname arg
1 parent e652eb8 commit 63b613b

File tree

6 files changed

+133
-83
lines changed

6 files changed

+133
-83
lines changed

examples/docs_snippets/docs_snippets/guides/components/index/1-help.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Usage: dg [OPTIONS] COMMAND [ARGS]...
2323
│ check Commands for checking the integrity of your Dagster code. │
2424
│ dev Start a local instance of Dagster. │
2525
│ docs Commands for generating docs from your Dagster code. │
26-
│ init Initialize a new Dagster project or workspace.
26+
│ init Initialize a new Dagster project, optionally inside a workspace.
2727
│ launch Launch a Dagster run. │
2828
│ list Commands for listing Dagster entities. │
2929
│ scaffold Commands for scaffolding Dagster code. │

examples/docs_snippets/docs_snippets/guides/dg/workspace/1-dg-init.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
dg init --workspace-name dagster-workspace
1+
dg init --workspace dagster-workspace
22

33
Scaffolded files for Dagster workspace at /.../dagster-workspace.
44
Enter the name of your Dagster project: project-1

examples/docs_snippets/docs_snippets_tests/snippet_checks/guides/dg/test_dg_docs_workspace.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def test_dg_docs_workspace(update_snippets: bool) -> None:
3232
with isolated_snippet_generation_environment() as get_next_snip_number:
3333
# Scaffold workspace
3434
run_command_and_snippet_output(
35-
cmd='echo "project-1\n" | dg init --use-editable-dagster --workspace-name dagster-workspace',
35+
cmd='echo "project-1\n" | dg init --use-editable-dagster --workspace dagster-workspace',
3636
snippet_path=DG_SNIPPETS_DIR / f"{get_next_snip_number()}-dg-init.txt",
3737
update_snippets=update_snippets,
3838
snippet_replace_regex=[
@@ -45,7 +45,7 @@ def test_dg_docs_workspace(update_snippets: bool) -> None:
4545
"of your Dagster project: project-1\n",
4646
),
4747
],
48-
print_cmd="dg init --workspace-name dagster-workspace",
48+
print_cmd="dg init --workspace dagster-workspace",
4949
)
5050

5151
# Remove files we don't want to show up in the tree

python_modules/libraries/dagster-dg/dagster_dg/cli/init.py

+59-29
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@
2323
@dg_editable_dagster_options
2424
@dg_global_options
2525
@click.option(
26-
"--workspace-name",
27-
type=str,
28-
help="Name of the workspace folder to create. If omitted, only a standalone project will be created.",
26+
"--workspace",
27+
is_flag=True,
28+
default=False,
29+
help="Create a workspace instead of a project at the given path.",
2930
)
3031
@click.option(
3132
"--project-name",
@@ -38,20 +39,44 @@
3839
type=click.Choice(["persistent_uv", "active"]),
3940
help="Type of Python environment in which to launch subprocesses for the project.",
4041
)
42+
@click.argument(
43+
"dirname",
44+
required=False,
45+
)
4146
@cli_telemetry_wrapper
4247
def init_command(
48+
dirname: Optional[str],
4349
use_editable_dagster: Optional[str],
44-
workspace_name: Optional[str],
50+
workspace: bool,
4551
project_name: Optional[str],
4652
project_python_environment: DgProjectPythonEnvironment,
4753
**global_options: object,
4854
):
49-
"""Initialize a new Dagster project or workspace.
55+
"""Initialize a new Dagster project, optionally inside a workspace.
56+
57+
By default, only a standalone project is created. The project will be created in a new directory specified by DIRNAME. If DIRNAME is not specified, the user will be prompted for it.
5058
51-
By default, only a standalone project is created. A workspace can also be created using the
52-
--workspace-name option.
59+
If the --workspace flag is passed, a workspace will be created in DIRNAME and a project created inside the workspace. If DIRNAME is not passed, the user will be prompted for it. The project name can be specified with the --project-name option, or the user will be prompted for it.
5360
54-
If a project is created, it will have the following structure:
61+
In either case, "." can be passed as DIRNAME to create the new project or workspace inside the existing working directory.
62+
63+
Examples:
64+
dg init
65+
Scaffold a new project in a new directory `<project_name>`. Prompts for `<project_name>`.
66+
dg init .
67+
Scaffold a new project in the CWD. The project name is taken from the last component of the CWD.
68+
dg init PROJECT_NAME
69+
Scaffold a new project in new directory PROJECT_NAME.
70+
dg init --workspace
71+
Scaffold a new workspace in a new directory `<workspace_name>`. Prompts for `<workspace_name>`. Scaffold a new project inside this workspace at projects/<project_name>. Prompts for `<project_name>`.
72+
dg init --workspace .
73+
Scaffold a new workspace in the CWD. Scaffold a new project inside this workspace at projects/<project_name>. Prompts for `<project_name>`.
74+
dg init -workspace WORKSPACE_NAME
75+
Scaffold a new workspace in a new directory WORKSPACE_NAME. Scaffold a new project inside this workspace at projects/<project_name>. Prompt for the project name.
76+
dg init --workspace --project-name PROJECT_NAME .
77+
Scaffold a new workspace in the CWD. Scaffold a new project inside this workspace at projects/PROJECT_NAME.
78+
79+
Created projects will have the following structure:
5580
5681
├── src
5782
│ └── <project_name>
@@ -77,39 +102,44 @@ def init_command(
77102
"""
78103
cli_config = normalize_cli_config(global_options, click.get_current_context())
79104

80-
workspace_path = None
105+
workspace_dirname = None
81106

82-
if workspace_name:
107+
if workspace:
83108
workspace_config = DgRawWorkspaceConfig(
84109
scaffold_project_options=DgWorkspaceScaffoldProjectOptions.get_raw_from_cli(
85110
use_editable_dagster,
86111
)
87112
)
88-
89-
workspace_path = scaffold_workspace(workspace_name, workspace_config)
90-
91-
if project_name is None:
92-
project_name = click.prompt(
93-
"Enter the name of your Dagster project",
94-
type=str,
95-
show_default=False,
96-
).strip()
97-
assert project_name is not None, "click.prompt returned None"
98-
else:
99-
project_name = project_name.strip()
100-
101-
if workspace_path:
113+
workspace_dirname = dirname or click.prompt(
114+
"Enter directory name for new workspace (. to use cwd)", default=".", type=str
115+
)
116+
workspace_dirname = scaffold_workspace(workspace_dirname, workspace_config)
117+
specified_project_name = project_name
102118
dg_context = DgContext.from_file_discovery_and_command_line_config(
103-
workspace_path, cli_config
119+
workspace_dirname, cli_config
104120
)
105-
project_path = Path(workspace_path, _DEFAULT_INIT_PROJECTS_DIR, project_name)
106-
121+
project_container = Path(workspace_dirname, _DEFAULT_INIT_PROJECTS_DIR)
107122
else:
123+
if dirname and project_name:
124+
exit_with_error(
125+
"Cannot specify both a DIRNAME and --project-name if --workspace is not passed."
126+
)
127+
specified_project_name = dirname or project_name
108128
dg_context = DgContext.from_file_discovery_and_command_line_config(Path.cwd(), cli_config)
129+
project_container = Path.cwd()
109130

110-
project_path = Path(Path.cwd(), project_name)
131+
project_name = (
132+
specified_project_name
133+
or click.prompt(
134+
"Enter the name of your Dagster project",
135+
type=str,
136+
show_default=False,
137+
).strip()
138+
)
139+
assert project_name is not None, "click.prompt returned None"
140+
project_path = project_container if project_name == "." else project_container / project_name
111141

112-
if project_path.exists():
142+
if project_name != "." and project_path.exists():
113143
exit_with_error(f"A file or directory already exists at {project_path}.")
114144

115145
scaffold_project(

python_modules/libraries/dagster-dg/dagster_dg/scaffold.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,18 @@
3333

3434

3535
def scaffold_workspace(
36-
workspace_name: str,
36+
dirname: str,
3737
workspace_config: Optional[DgRawWorkspaceConfig] = None,
3838
) -> Path:
3939
# Can't create a workspace that is a child of another workspace
40-
new_workspace_path = Path.cwd() / workspace_name
40+
41+
new_workspace_path = Path.cwd() if dirname == "." else Path.cwd() / dirname
4142
existing_workspace_path = discover_workspace_root(new_workspace_path)
4243
if existing_workspace_path:
4344
exit_with_error(
4445
f"Workspace already exists at {existing_workspace_path}. Run `dg scaffold project` to add a new project to that workspace."
4546
)
46-
elif new_workspace_path.exists():
47+
elif dirname != "." and new_workspace_path.exists():
4748
exit_with_error(f"Folder already exists at {new_workspace_path}.")
4849

4950
scaffold_subtree(
@@ -52,7 +53,7 @@ def scaffold_workspace(
5253
templates_path=os.path.join(
5354
os.path.dirname(__file__), "templates", "WORKSPACE_NAME_PLACEHOLDER"
5455
),
55-
project_name=workspace_name,
56+
project_name=dirname,
5657
)
5758

5859
if workspace_config is not None:

python_modules/libraries/dagster-dg/dagster_dg_tests/cli_tests/test_init_command.py

+65-46
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os
22
from pathlib import Path
3-
from typing import get_args
3+
from typing import Optional, get_args
44

55
import pytest
66
import tomlkit
@@ -17,32 +17,85 @@
1717
from dagster_dg_tests.utils import ProxyRunner, assert_runner_result
1818

1919

20-
def test_init_command_success(monkeypatch) -> None:
20+
@pytest.mark.parametrize(
21+
"cli_args,input_str",
22+
[
23+
(("--", "."), None),
24+
(("--", "helloworld"), None),
25+
(("--project-name", "helloworld"), None),
26+
(tuple(), "helloworld\n"),
27+
],
28+
ids=["dirname_cwd", "dirname_arg", "project_name_opt", "project_name_prompt"],
29+
)
30+
def test_init_success_no_workspace(
31+
monkeypatch, cli_args: tuple[str, ...], input_str: Optional[str]
32+
) -> None:
2133
dagster_git_repo_dir = discover_git_root(Path(__file__))
2234
monkeypatch.setenv("DAGSTER_GIT_REPO_DIR", str(dagster_git_repo_dir))
2335
with ProxyRunner.test() as runner, runner.isolated_filesystem():
24-
result = runner.invoke("init", "--use-editable-dagster", input="helloworld\n")
36+
if "." in cli_args: # creating in CWD
37+
os.mkdir("helloworld")
38+
os.chdir("helloworld")
39+
40+
result = runner.invoke(
41+
"init",
42+
"--project-python-environment",
43+
"active",
44+
"--use-editable-dagster",
45+
*cli_args,
46+
input=input_str,
47+
)
2548
assert_runner_result(result)
26-
assert not Path("dagster-workspace").exists()
2749

50+
if "." in cli_args: # creating in CWD
51+
os.chdir("..")
52+
53+
assert not Path("dagster-workspace").exists()
2854
assert Path("helloworld").exists()
2955
assert Path("helloworld/src/helloworld").exists()
3056
assert Path("helloworld/pyproject.toml").exists()
3157
assert Path("helloworld/tests").exists()
3258

3359

34-
def test_init_command_success_with_workspace_name(monkeypatch) -> None:
60+
@pytest.mark.parametrize(
61+
"cli_args,input_str",
62+
[
63+
(("--project-name", "helloworld", "."), None),
64+
(("--project-name", "helloworld", "dagster-workspace"), None),
65+
(("--", "dagster-workspace"), "helloworld\n"),
66+
(tuple(), "dagster-workspace\nhelloworld\n"),
67+
],
68+
ids=[
69+
"dirname_cwd_and_project_name",
70+
"dirname_arg_and_project_name",
71+
"dirname_arg_no_project_name",
72+
"no_dirname_arg_no_project_name",
73+
],
74+
)
75+
def test_init_success_workspace(
76+
monkeypatch, cli_args: tuple[str, ...], input_str: Optional[str]
77+
) -> None:
3578
dagster_git_repo_dir = discover_git_root(Path(__file__))
3679
monkeypatch.setenv("DAGSTER_GIT_REPO_DIR", str(dagster_git_repo_dir))
3780
with ProxyRunner.test() as runner, runner.isolated_filesystem():
81+
if "." in cli_args: # creating in CWD
82+
os.mkdir("dagster-workspace")
83+
os.chdir("dagster-workspace")
84+
3885
result = runner.invoke(
3986
"init",
40-
"--workspace-name",
41-
"dagster-workspace",
87+
"--workspace",
88+
"--project-python-environment",
89+
"active",
4290
"--use-editable-dagster",
43-
input="helloworld\n",
91+
*cli_args,
92+
input=input_str,
4493
)
4594
assert_runner_result(result)
95+
96+
if "." in cli_args: # creating in CWD
97+
os.chdir("..")
98+
4699
assert Path("dagster-workspace").exists()
47100
assert Path("dagster-workspace/dg.toml").exists()
48101
assert Path("dagster-workspace/projects").exists()
@@ -59,40 +112,6 @@ def test_init_command_success_with_workspace_name(monkeypatch) -> None:
59112
)
60113

61114

62-
def test_init_override_project_name_prompt_with_workspace(monkeypatch) -> None:
63-
with ProxyRunner.test() as runner, runner.isolated_filesystem():
64-
result = runner.invoke(
65-
"init",
66-
"--project-python-environment",
67-
"active",
68-
"--project-name",
69-
"goodbyeworld",
70-
"--workspace-name",
71-
"my-workspace",
72-
)
73-
assert_runner_result(result)
74-
assert Path("my-workspace").exists()
75-
assert Path("my-workspace/dg.toml").exists()
76-
assert Path("my-workspace/projects").exists()
77-
assert Path("my-workspace/libraries").exists()
78-
assert Path("my-workspace/projects/goodbyeworld").exists()
79-
assert Path("my-workspace/projects/goodbyeworld/src/goodbyeworld").exists()
80-
assert Path("my-workspace/projects/goodbyeworld/pyproject.toml").exists()
81-
assert Path("my-workspace/projects/goodbyeworld/tests").exists()
82-
83-
84-
def test_init_override_project_name_prompt_without_workspace(monkeypatch) -> None:
85-
dagster_git_repo_dir = discover_git_root(Path(__file__))
86-
monkeypatch.setenv("DAGSTER_GIT_REPO_DIR", str(dagster_git_repo_dir))
87-
with ProxyRunner.test() as runner, runner.isolated_filesystem():
88-
result = runner.invoke("init", "--project-name", "goodbyeworld", "--use-editable-dagster")
89-
assert_runner_result(result)
90-
assert Path("goodbyeworld").exists()
91-
assert Path("goodbyeworld/src/goodbyeworld").exists()
92-
assert Path("goodbyeworld/pyproject.toml").exists()
93-
assert Path("goodbyeworld/tests").exists()
94-
95-
96115
def test_init_workspace_already_exists_failure(monkeypatch) -> None:
97116
dagster_git_repo_dir = discover_git_root(Path(__file__))
98117
monkeypatch.setenv("DAGSTER_GIT_REPO_DIR", str(dagster_git_repo_dir))
@@ -102,7 +121,7 @@ def test_init_workspace_already_exists_failure(monkeypatch) -> None:
102121
result = runner.invoke(
103122
"init",
104123
"--use-editable-dagster",
105-
"--workspace-name",
124+
"--workspace",
106125
"dagster-workspace",
107126
input="\nhelloworld\n",
108127
)
@@ -116,7 +135,7 @@ def test_init_project_already_exists_failure(monkeypatch) -> None:
116135

117136
with ProxyRunner.test() as runner, runner.isolated_filesystem():
118137
os.mkdir("foo")
119-
result = runner.invoke("init", "--use-editable-dagster", input="foo\n")
138+
result = runner.invoke("init", "--use-editable-dagster", "--", "foo")
120139
assert_runner_result(result, exit_0=False)
121140
assert "already exists" in result.output
122141

@@ -134,9 +153,9 @@ def test_init_use_editable_dagster(option: EditableOption, value_source: str, mo
134153
with ProxyRunner.test() as runner, runner.isolated_filesystem():
135154
result = runner.invoke(
136155
"init",
137-
"--workspace-name",
138-
"dagster-workspace",
156+
"--workspace",
139157
*editable_args,
158+
"dagster-workspace",
140159
input="helloworld\n",
141160
)
142161
assert_runner_result(result)

0 commit comments

Comments
 (0)