Skip to content

Commit fca31db

Browse files
author
Daniel Molnar
committed
Add data-path, extend tests, update readme
1 parent c29a1ac commit fca31db

8 files changed

Lines changed: 300 additions & 21 deletions

File tree

README.md

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ composer-dev create \
139139
--port WEB_SERVER_PORT \
140140
--dags-path LOCAL_DAGS_PATH \
141141
--plugins-path LOCAL_PLUGINS_PATH \
142+
--data-path LOCAL_DATA_PATH \
142143
--database DATABASE_ENGINE \
143144
LOCAL_ENVIRONMENT_NAME
144145
```
@@ -152,6 +153,9 @@ Replace:
152153
located.
153154
- `LOCAL_PLUGINS_PATH` with the path to a local directory where the plugins
154155
files are located.
156+
- `LOCAL_DATA_PATH` with the path to a local directory where the data files are
157+
located. If omitted, a `data` directory inside the environment directory is
158+
used.
155159
- `DATABASE_ENGINE` with the database engine you wanted to use. You can use
156160
`sqlite` or `postgresql` (default).
157161
- `LOCAL_ENVIRONMENT_NAME` with the name of this local Airflow environment.
@@ -200,7 +204,8 @@ composer-dev create LOCAL_ENVIRONMENT_NAME \
200204
--project PROJECT_ID \
201205
--port WEB_SERVER_PORT \
202206
--dags-path LOCAL_DAGS_PATH \
203-
--plugins-path LOCAL_PLUGINS_PATH
207+
--plugins-path LOCAL_PLUGINS_PATH \
208+
--data-path LOCAL_DATA_PATH
204209
```
205210
206211
Replace:
@@ -215,6 +220,9 @@ Replace:
215220
located.
216221
- `LOCAL_PLUGINS_PATH` with a path to a local directory where the plugins are
217222
located.
223+
- `LOCAL_DATA_PATH` with a path to a local directory where the data files are
224+
located. If omitted, a `data` directory inside the environment directory is
225+
used.
218226
219227
Example:
220228
@@ -302,17 +310,20 @@ composer-dev stop LOCAL_ENVIRONMENT_NAME
302310
303311
## Add and update DAGs
304312
305-
DAGs and plugins are stored in the directories that you specified in the
306-
`--dags-path` and `--plugins-path` parameters respectively when you created
307-
your local Airflow environment. By default, these directories are
308-
`./composer/<local_environment_name>/dags` and
309-
`./composer/<local_environment_name>/plugins`.
313+
DAGs, plugins, and data are stored in the directories that you specified in the
314+
`--dags-path`, `--plugins-path`, and `--data-path` parameters respectively when
315+
you created your local Airflow environment. By default, these directories are
316+
`./composer/<local_environment_name>/dags`,
317+
`./composer/<local_environment_name>/plugins`, and
318+
`./composer/<local_environment_name>/data`. The data directory is mounted into
319+
the container at `/home/airflow/gcs/data`.
310320
311321
You can get the directories used by your environment with the
312322
[`describe` command](#get-a-list-and-status-of-local-airflow-environments).
313323
314-
To add and update DAGs and plugins, change files in these directories. You do
315-
not need to restart your local Airflow environment for changes to take effect.
324+
To add and update DAGs, plugins, and data, change files in these directories.
325+
You do not need to restart your local Airflow environment for changes to take
326+
effect.
316327
317328
## View local Airflow environment logs
318329

composer_local_dev/cli.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,12 @@
4848
},
4949
{
5050
"name": "Environment options",
51-
"options": ["--web-server-port", "--dags-path", "--plugins-path"],
51+
"options": [
52+
"--web-server-port",
53+
"--dags-path",
54+
"--plugins-path",
55+
"--data-path",
56+
],
5257
},
5358
{
5459
"name": "Container Memory and CPUs limit",
@@ -260,6 +265,13 @@ def _complete_environment(ctx, param, incomplete):
260265
metavar="PATH",
261266
type=click.Path(file_okay=False),
262267
)
268+
@click.option(
269+
"--data-path",
270+
help="Path to data folder. If it does not exist, it will be created.",
271+
show_default="'data' directory in the environment directory",
272+
metavar="PATH",
273+
type=click.Path(file_okay=False),
274+
)
263275
@click.option(
264276
"--database-engine",
265277
"--database",
@@ -285,6 +297,7 @@ def create(
285297
database_engine: str,
286298
dags_path: Optional[pathlib.Path] = None,
287299
plugins_path: Optional[pathlib.Path] = None,
300+
data_path: Optional[pathlib.Path] = None,
288301
container_memory_limit: Optional[str] = None,
289302
container_cpu_limit: Optional[str] = None,
290303
):
@@ -341,6 +354,7 @@ def create(
341354
web_server_port=web_server_port,
342355
dags_path=dags_path,
343356
plugins_path=plugins_path,
357+
data_path=data_path,
344358
database_engine=database_engine,
345359
memory_limit=container_memory_limit,
346360
cpu_count=container_cpu_limit,
@@ -354,6 +368,7 @@ def create(
354368
port=web_server_port,
355369
dags_path=dags_path,
356370
plugins_path=plugins_path,
371+
data_path=data_path,
357372
database_engine=database_engine,
358373
memory_limit=container_memory_limit,
359374
cpu_count=container_cpu_limit,

composer_local_dev/constants.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ def choices(cls):
9494
9595
1. You can put your DAGs in {dags_path}
9696
2. You can put your plugins in {plugins_path}
97-
3. Access Airflow at http://localhost:{port}
97+
3. You can put your data in {data_path}
98+
4. Access Airflow at http://localhost:{port}
9899
"""
99100
# TODO: Fill source environment info
100101
DESCRIBE_ENV_MESSAGE = """
@@ -103,6 +104,7 @@ def choices(cls):
103104
Image version: {image_version}
104105
Dags directory: {dags_path}.
105106
Plugins directory: {plugins_path}.
107+
Data directory: {data_path}.
106108
The environment is using credentials from gcloud located at {gcloud_path}.
107109
"""
108110
KUBECONFIG_PATH_MESSAGE = """
@@ -142,6 +144,15 @@ def choices(cls):
142144
PLUGINS_PATH_NOT_EXISTS_ERROR = (
143145
"Plugins path does not exist or is not a directory: {plugins_path}"
144146
)
147+
CREATING_DATA_PATH_WARN = (
148+
"Data path '{data_path}' does not exist. It will be created."
149+
)
150+
DATA_PATH_NOT_PROVIDED_WARN = (
151+
"No data directory provided, using default data directory."
152+
)
153+
DATA_PATH_NOT_EXISTS_ERROR = (
154+
"Data path does not exist or is not a directory: {data_path}"
155+
)
145156
FAILED_TO_GET_DOCKER_PORT_WARN = (
146157
"Failed to retrieve used port from the Docker daemon, "
147158
"using port from the environment configuration."

composer_local_dev/environment.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ def get_image_mounts(
5252
kube_config_path: Optional[str],
5353
requirements: pathlib.Path,
5454
database_mounts: Dict[pathlib.Path, str],
55+
data_path: Optional[str] = None,
5556
) -> List[docker.types.Mount]:
5657
"""
5758
Return list of docker volumes to be mounted inside container.
@@ -63,11 +64,14 @@ def get_image_mounts(
6364
- environment airflow sqlite db file location
6465
- database_mounts which contains the path for database mounts
6566
"""
67+
# Backwards compatibility: when no explicit data_path is given, the data
68+
# directory lives inside the environment directory (the original behaviour).
69+
data_path = data_path if data_path is not None else env_path / "data"
6670
mount_paths = {
6771
requirements: "composer_requirements.txt",
6872
dags_path: "gcs/dags/",
6973
plugins_path: "gcs/plugins/",
70-
env_path / "data": "gcs/data/",
74+
data_path: "gcs/data/",
7175
gcloud_config_path: ".config/gcloud",
7276
**database_mounts,
7377
}
@@ -373,6 +377,11 @@ def __init__(self, env_dir_path: pathlib.Path, port: Optional[int]):
373377
self.plugins_path = self.get_str_param("plugins_path")
374378
else:
375379
self.plugins_path = files.resolve_plugins_path(None, env_dir_path)
380+
# Backwards compatibility: don't fail on missing data_path
381+
if "data_path" in self.config:
382+
self.data_path = self.get_str_param("data_path")
383+
else:
384+
self.data_path = files.resolve_data_path(None, env_dir_path)
376385
self.dag_dir_list_interval = self.parse_int_param(
377386
"dag_dir_list_interval", allowed_range=(0,)
378387
)
@@ -457,6 +466,7 @@ def __init__(
457466
location: str,
458467
dags_path: Optional[str],
459468
plugins_path: Optional[str] = None,
469+
data_path: Optional[str] = None,
460470
dag_dir_list_interval: int = 10,
461471
database_engine: str = constants.DatabaseEngine.postgresql,
462472
memory_limit: Optional[str] = None,
@@ -488,6 +498,7 @@ def __init__(
488498
self.plugins_path = files.resolve_plugins_path(
489499
plugins_path, env_dir_path
490500
)
501+
self.data_path = files.resolve_data_path(data_path, env_dir_path)
491502
self.dag_dir_list_interval = dag_dir_list_interval
492503
self.database_engine = database_engine
493504
self.is_database_sqlite3 = (
@@ -563,6 +574,7 @@ def load_from_config(cls, env_dir_path: pathlib.Path, port: Optional[int]):
563574
location=config.location,
564575
dags_path=config.dags_path,
565576
plugins_path=config.plugins_path,
577+
data_path=config.data_path,
566578
dag_dir_list_interval=config.dag_dir_list_interval,
567579
port=config.port,
568580
database_engine=config.database_engine,
@@ -582,6 +594,7 @@ def from_source_environment(
582594
dags_path: Optional[str],
583595
plugins_path: Optional[str],
584596
database_engine: str,
597+
data_path: Optional[str] = None,
585598
memory_limit: Optional[str] = None,
586599
cpu_count: Optional[int] = None,
587600
):
@@ -606,6 +619,7 @@ def from_source_environment(
606619
location=location,
607620
dags_path=dags_path,
608621
plugins_path=plugins_path,
622+
data_path=data_path,
609623
dag_dir_list_interval=10,
610624
port=web_server_port,
611625
pypi_packages=pypi_packages,
@@ -720,6 +734,7 @@ def write_environment_config_to_config_file(self):
720734
"composer_project_id": self.project_id,
721735
"dags_path": self.dags_path,
722736
"plugins_path": self.plugins_path,
737+
"data_path": self.data_path,
723738
"dag_dir_list_interval": int(self.dag_dir_list_interval),
724739
"port": int(self.port),
725740
"database_engine": self.database_engine,
@@ -802,6 +817,7 @@ def create_docker_container(self):
802817
utils.resolve_kube_config_path(),
803818
self.requirements_file,
804819
db_mounts,
820+
self.data_path,
805821
)
806822
db_vars = db_extras["env_vars"]
807823
default_vars = self.get_default_environment_variables(db_vars)
@@ -886,6 +902,7 @@ def create_db_docker_container(self):
886902
utils.resolve_kube_config_path(),
887903
self.requirements_file,
888904
db_mounts,
905+
self.data_path,
889906
)
890907
db_vars = db_extras["env_vars"]
891908
db_ports = db_extras["ports"]
@@ -967,7 +984,10 @@ def create(self):
967984
assert_image_exists(self.image_version)
968985
self.assert_valid_environment_options()
969986
files.create_environment_directories(
970-
self.env_dir_path, self.dags_path, self.plugins_path
987+
self.env_dir_path,
988+
self.dags_path,
989+
self.plugins_path,
990+
self.data_path,
971991
)
972992
self.create_database_files(skip_if_exist=False)
973993
self.write_environment_config_to_config_file()
@@ -1104,6 +1124,7 @@ def start(self, assert_not_running=True):
11041124
self.assert_requirements_exist()
11051125
files.assert_dag_path_exists(self.dags_path)
11061126
files.assert_plugins_path_exists(self.plugins_path)
1127+
files.assert_data_path_exists(self.data_path)
11071128

11081129
self.create_database_files()
11091130
db_path = (
@@ -1153,6 +1174,7 @@ def print_start_message(self):
11531174
env_name=self.name,
11541175
dags_path=self.dags_path,
11551176
plugins_path=self.plugins_path,
1177+
data_path=self.data_path,
11561178
port=self.port,
11571179
)
11581180
)
@@ -1278,6 +1300,7 @@ def prepare_env_description(self, env_status: str) -> str:
12781300
image_version=self.image_version,
12791301
dags_path=self.dags_path,
12801302
plugins_path=self.plugins_path,
1303+
data_path=self.data_path,
12811304
gcloud_path=utils.resolve_gcloud_config_path(),
12821305
)
12831306
+ (

composer_local_dev/errors.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,15 @@ def __init__(self, plugins_path):
191191
)
192192

193193

194+
class DataPathNotExistError(ComposerCliError):
195+
"""Data path does not exist or is not a directory."""
196+
197+
def __init__(self, data_path):
198+
super().__init__(
199+
constants.DATA_PATH_NOT_EXISTS_ERROR.format(data_path=data_path)
200+
)
201+
202+
194203
def catch_exceptions(func=None):
195204
"""
196205
Catch exceptions and print user friendly message for common issues.

composer_local_dev/files.py

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -126,23 +126,46 @@ def resolve_plugins_path(
126126
return str(plugins_path.resolve())
127127

128128

129+
def resolve_data_path(data_path: Optional[str], env_dir: pathlib.Path) -> str:
130+
"""
131+
Provides and validates path to the data directory.
132+
If ``data_path`` is None, the path is constructed from ``env_dir`` path
133+
and ``data`` directory.
134+
If ``data_path`` is not None, but it does not exist, a warning is raised.
135+
136+
Returns absolute ``data_path`` path.
137+
"""
138+
if data_path is None:
139+
console.get_console().print(constants.DATA_PATH_NOT_PROVIDED_WARN)
140+
data_path = env_dir / "data"
141+
else:
142+
data_path = pathlib.Path(data_path)
143+
return str(data_path.resolve())
144+
145+
129146
def create_environment_directories(
130-
env_dir: pathlib.Path, dags_path: str, plugins_path: str
147+
env_dir: pathlib.Path,
148+
dags_path: str,
149+
plugins_path: str,
150+
data_path: Optional[str] = None,
131151
):
132152
"""
133153
Create environment directories (overwriting existing ones).
134154
Environment directory is a directory which contains configuration files for
135155
composer local environment and files used by environment such as
136156
requirements.txt file, dags, data and plugins directories.
137157
"""
138-
data_dir = "data"
139-
LOG.info(
140-
"Creating environment directory %s in " "%s environment directory.",
141-
data_dir,
142-
env_dir,
158+
# Backwards compatibility: when no explicit data_path is given, the data
159+
# directory lives inside the environment directory (the original behaviour).
160+
data_path = (
161+
pathlib.Path(data_path) if data_path is not None else env_dir / "data"
143162
)
144163
env_dir.mkdir(exist_ok=True, parents=True)
145-
(env_dir / data_dir).mkdir(exist_ok=True)
164+
if not data_path.is_dir():
165+
console.get_console().print(
166+
constants.CREATING_DATA_PATH_WARN.format(data_path=data_path)
167+
)
168+
data_path.mkdir(parents=True)
146169

147170
dags_path = pathlib.Path(dags_path)
148171
if not dags_path.is_dir():
@@ -259,3 +282,10 @@ def assert_plugins_path_exists(path: str) -> None:
259282
if pathlib.Path(path).is_dir():
260283
return
261284
raise errors.PluginsPathNotExistError(path)
285+
286+
287+
def assert_data_path_exists(path: str) -> None:
288+
"""Raise an error if data path does not point to existing directory."""
289+
if pathlib.Path(path).is_dir():
290+
return
291+
raise errors.DataPathNotExistError(path)

0 commit comments

Comments
 (0)