Skip to content

Commit c620508

Browse files
authored
grass.app: Add project create subcommand (#6441)
The create_project function is readily available for CLI use especially with the new crs parameter (added in #6415), so this is adding the functionality under as `project create` subcommand. All create_project parameters are exposed, so the user is not limited to what crs parameter can currently handle. This will be useful in tests of the subcommand CLI itself if we want to use the other subcommands rather than the Python functions in the tests (of subcommands). It is used for lock and unlock tests in #6437. A --project parameter is added to the run subcommand which now can use existing project instead of creating a new one. This makes subcommand `run --project project/path ...` roughly equivalent to `grass project/path --exec ...`. The functionality is used in the test to check the resulting project CRS. Example subcomands: project create --crs EPSG:3358 /tmp/project_1 run --project /tmp/project_1 g.mapset -p Adds missing documentation for crs parameter of create_project. Also, this uses the description parameter from #6440 not desc. Implementation of the subcommand now uses StackExit context manager to handle the only-sometimes-needed temporary directory.
1 parent a399ea3 commit c620508

File tree

3 files changed

+110
-5
lines changed

3 files changed

+110
-5
lines changed

python/grass/app/cli.py

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@
2121
import os
2222
import sys
2323
import subprocess
24+
from contextlib import ExitStack
2425
from pathlib import Path
2526

2627

2728
import grass.script as gs
2829
from grass.app.data import lock_mapset, unlock_mapset, MapsetLockingException
30+
from grass.exceptions import ScriptError
2931
from grass.tools import Tools
3032

3133
# Special flags supported besides help and --json which does not need special handling:
@@ -39,12 +41,16 @@
3941
# --help-text --html-description --rst-description
4042

4143

42-
def subcommand_run_tool(args, tool_args: list, print_help: bool):
44+
def subcommand_run_tool(args, tool_args: list, print_help: bool) -> int:
4345
command = [args.tool, *tool_args]
44-
with tempfile.TemporaryDirectory() as tmp_dir_name:
45-
project_name = "project"
46-
project_path = Path(tmp_dir_name) / project_name
47-
gs.create_project(project_path, crs=args.crs)
46+
with ExitStack() as stack:
47+
if args.project:
48+
project_path = Path(args.project)
49+
else:
50+
tmp_dir_name = stack.enter_context(tempfile.TemporaryDirectory())
51+
project_name = "project"
52+
project_path = Path(tmp_dir_name) / project_name
53+
gs.create_project(project_path, crs=args.crs)
4854
with gs.setup.init(project_path) as session:
4955
tools = Tools(
5056
session=session, capture_output=False, consistent_return_value=True
@@ -66,6 +72,25 @@ def subcommand_run_tool(args, tool_args: list, print_help: bool):
6672
return error.returncode
6773

6874

75+
def subcommand_create_project(args) -> int:
76+
"""Translates args to function parameters"""
77+
try:
78+
gs.create_project(
79+
path=args.path,
80+
crs=args.crs,
81+
proj4=args.proj4,
82+
wkt=args.wkt,
83+
datum=args.datum,
84+
datum_trans=args.datum_trans,
85+
description=args.description,
86+
overwrite=args.overwrite,
87+
)
88+
except ScriptError as error:
89+
print(_("Error creating project: {}").format(error), file=sys.stderr)
90+
return 1
91+
return 0
92+
93+
6994
def subcommand_lock_mapset(args):
7095
gs.setup.setup_runtime_env()
7196
try:
@@ -103,6 +128,48 @@ def subcommand_show_man(args):
103128
return call_g_manual(entry=args.page, flags="m")
104129

105130

131+
def add_project_subparser(subparsers):
132+
project_parser = subparsers.add_parser("project", help="project operations")
133+
project_subparsers = project_parser.add_subparsers(dest="project_command")
134+
135+
create_parser = project_subparsers.add_parser("create", help="create project")
136+
create_parser.add_argument("path", help="path to the new project")
137+
create_parser.add_argument(
138+
"--crs",
139+
help="CRS for the project",
140+
)
141+
create_parser.add_argument(
142+
"--proj4",
143+
help="if given, create new project based on Proj4 definition",
144+
)
145+
create_parser.add_argument(
146+
"--wkt",
147+
help=(
148+
"if given, create new project based on WKT definition "
149+
"(can be path to PRJ file or WKT string)"
150+
),
151+
)
152+
create_parser.add_argument(
153+
"--datum",
154+
help="GRASS format datum code",
155+
)
156+
create_parser.add_argument(
157+
"--datum-trans",
158+
dest="datum_trans",
159+
help="datum transformation parameters (used for epsg and proj4)",
160+
)
161+
create_parser.add_argument(
162+
"--description",
163+
help="description of the project",
164+
)
165+
create_parser.add_argument(
166+
"--overwrite",
167+
action="store_true",
168+
help="overwrite existing project",
169+
)
170+
create_parser.set_defaults(func=subcommand_create_project)
171+
172+
106173
def main(args=None, program=None):
107174
# Top-level parser
108175
if program is None:
@@ -128,8 +195,13 @@ def main(args=None, program=None):
128195
run_subparser.add_argument("tool", type=str, nargs="?", help="name of a tool")
129196
run_subparser.add_argument("--help", action="store_true")
130197
run_subparser.add_argument("--crs", type=str, help="CRS to use for computations")
198+
run_subparser.add_argument(
199+
"--project", type=str, help="project to use for computations"
200+
)
131201
run_subparser.set_defaults(func=subcommand_run_tool)
132202

203+
add_project_subparser(subparsers)
204+
133205
subparser = subparsers.add_parser("lock", help="lock a mapset")
134206
subparser.add_argument("mapset_path", type=str)
135207
subparser.add_argument(

python/grass/app/tests/grass_app_cli_test.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,35 @@ def test_subcommand_run_with_crs_as_pack_subprocess(pack_raster_file4x5_rows, ca
127127
check=True,
128128
)
129129
assert json.loads(result.stdout)["srid"] == "EPSG:3358"
130+
131+
132+
def test_create_overwrite(tmp_path):
133+
"""Check that creating when project exists fails unless overwrite is True"""
134+
project = tmp_path / "test_1"
135+
assert main(["project", "create", str(project)]) == 0
136+
assert main(["project", "create", str(project)]) == 1
137+
assert main(["project", "create", "--overwrite", str(project)]) == 0
138+
139+
140+
@pytest.mark.parametrize("crs", ["EPSG:4326", "EPSG:3358"])
141+
def test_create_crs_epsg(tmp_path, crs):
142+
"""Check that created project has the requested EPSG"""
143+
project = tmp_path / "test_1"
144+
assert main(["project", "create", str(project), "--crs", crs]) == 0
145+
result = subprocess.run(
146+
[
147+
sys.executable,
148+
"-m",
149+
"grass.app",
150+
"run",
151+
"--project",
152+
str(project),
153+
"g.proj",
154+
"-p",
155+
"format=json",
156+
],
157+
capture_output=True,
158+
text=True,
159+
check=True,
160+
)
161+
assert json.loads(result.stdout)["srid"] == crs

python/grass/script/core.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1962,6 +1962,7 @@ def create_project(
19621962
:param str path: path to GRASS database or project; if path to database, project
19631963
name must be specified with name parameter
19641964
:param str name: project name to create
1965+
:param crs: CRS of the new project EPSG or filename (defaults to 'XY')
19651966
:param epsg: if given create new project based on EPSG code
19661967
:param proj4: if given create new project based on Proj4 definition
19671968
:param str filename: if given create new project based on georeferenced file

0 commit comments

Comments
 (0)