Skip to content

Commit 4de49a6

Browse files
authored
rearrange gra-api-create (#41)
1 parent b1e1dd8 commit 4de49a6

File tree

10 files changed

+321
-367
lines changed

10 files changed

+321
-367
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
Added
3+
-----
4+
5+
* Add Access Role Control to ``gra api create``.
6+
7+
Usage Example:
8+
9+
.. code-block:: console
10+
11+
gra api create "My API" \
12+
--target ./target-json-structure.json \
13+
--description "My Cool API" \
14+
--owner "urn:globus:auth:identity:0b8067fc-0bb4-46e4-b23d-3ad543624519" \
15+
--admin "urn:globus:auth:identity:d86ff962-1b2a-4de8-8bde-7dc993494dcb" \
16+
--admin "urn:globus:groups:id:b0d11f00-5701-480f-a523-5b03869dfdbc" \
17+
--viewer "urn:globus:groups:id:ed3219c3-c4ef-4b04-932a-d00bf88ceea7"
18+
19+
Changed
20+
-------
21+
22+
* Change the ``gra api create`` command to accept a target file, instead of
23+
constructing one from a supplied openapi specification.
24+
25+
New Usage Example:
26+
27+
.. code-block:: console
28+
29+
gra api create "My API" \
30+
--target ./target-json-structure.json \
31+
--description "My Cool API"
32+
33+
* Change the ``gra api update`` command to accept a target file with the option
34+
``--target`` instead of ``target-file``.
35+
36+
Development
37+
-----------
38+
39+
* Centralize registered api printing logic into a command-shared function.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# This file is a part of globus-registered-api.
2+
# https://github.com/globus/globus-registered-api
3+
# Copyright 2025-2026 Globus <support@globus.org>
4+
# SPDX-License-Identifier: Apache-2.0
5+
6+
import json
7+
8+
import click
9+
from globus_sdk import GlobusHTTPResponse
10+
11+
12+
def echo_registered_api(response: GlobusHTTPResponse, format: str) -> None:
13+
if format == "json":
14+
click.echo(json.dumps(response.data, indent=2))
15+
else:
16+
click.echo(f"ID: {response['id']}")
17+
click.echo(f"Name: {response['name']}")
18+
click.echo(f"Description: {response['description']}")
19+
_echo_list("Owners: ", response["roles"]["owners"])
20+
_echo_list("Administrators: ", response["roles"]["administrators"])
21+
_echo_list("Viewers: ", response["roles"]["viewers"])
22+
click.echo(f"Created: {response['created_timestamp']}")
23+
click.echo(f"Updated: {response['updated_timestamp']}")
24+
25+
26+
def _echo_list(key: str, items: list[str]) -> None:
27+
"""
28+
Print a list of items, indented to a common length (the length of the key).
29+
"""
30+
31+
if not items:
32+
click.echo(f"{key}")
33+
return
34+
35+
# Print the first item prefixed with the key.
36+
prefix = key
37+
for item in items:
38+
click.echo(f"{prefix}{item}")
39+
# Print subsequent items prefixed with spaces to align with the first item.
40+
prefix = " " * len(key)

src/globus_registered_api/commands/api/create.py

Lines changed: 39 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,60 @@
44
# SPDX-License-Identifier: Apache-2.0
55

66
import json
7+
import pathlib
78

89
import click
910

1011
from globus_registered_api.clients import create_flows_client
12+
from globus_registered_api.commands.api._common import echo_registered_api
1113
from globus_registered_api.context import CLIContext
1214
from globus_registered_api.context import with_cli_context
13-
from globus_registered_api.domain import HTTP_METHODS
14-
from globus_registered_api.domain import TargetSpecifier
15-
from globus_registered_api.openapi import AmbiguousContentTypeError
16-
from globus_registered_api.openapi import OpenAPILoadError
17-
from globus_registered_api.openapi import TargetNotFoundError
18-
from globus_registered_api.openapi import process_target
1915

2016

2117
@click.command("create")
22-
@click.argument("openapi_spec")
23-
@click.argument("method", type=click.Choice(HTTP_METHODS, case_sensitive=False))
24-
@click.argument("route")
2518
@click.argument("name")
2619
@click.option(
27-
"--content-type",
28-
default="*",
29-
help="Target content-type for request body (required if multiple exist)",
20+
"--target",
21+
required=True,
22+
type=click.Path(exists=True, dir_okay=False, readable=True, path_type=pathlib.Path),
23+
help="Filepath to a JSON object containing the target definition",
3024
)
3125
@click.option(
3226
"--description",
3327
required=True,
3428
help="Description for the registered API",
3529
)
30+
@click.option(
31+
"--owner",
32+
"owners",
33+
multiple=True,
34+
help="Set owner URN (can specify multiple, can only be set by owners)",
35+
)
36+
@click.option(
37+
"--administrator",
38+
"administrators",
39+
multiple=True,
40+
help="Set administrator URN (can specify multiple, can only be set by owners)",
41+
)
42+
@click.option(
43+
"--viewer",
44+
"viewers",
45+
multiple=True,
46+
help=(
47+
"Set viewer URN (can specify multiple, can only be set by owners "
48+
"and administrators)"
49+
),
50+
)
3651
@click.option("--format", type=click.Choice(["json", "text"]), default="text")
3752
@with_cli_context
3853
def create_command(
3954
ctx: CLIContext,
40-
openapi_spec: str,
41-
method: str,
42-
route: str,
55+
target: pathlib.Path,
4356
name: str,
44-
content_type: str,
4557
description: str,
58+
owners: tuple[str, ...],
59+
administrators: tuple[str, ...],
60+
viewers: tuple[str, ...],
4661
format: str,
4762
) -> None:
4863
"""
@@ -51,41 +66,23 @@ def create_command(
5166
Extracts a target endpoint from an OpenAPI spec and registers it with
5267
the Flows service.
5368
54-
OPENAPI_SPEC - A filepath or URL to an OpenAPI specification (JSON or YAML).
55-
56-
METHOD - Target API's HTTP method (e.g., get, post, put, delete).
57-
58-
ROUTE - Target API's route path (e.g., /items or /items/{item_id}).
59-
60-
NAME - Name for the registered API.
69+
NAME - Name of the new registered API.
6170
6271
Example:
6372
6473
\b
65-
gra api create ./spec.json get /items "My API" --description "My API"
74+
gra api create "My API" --target ./target.json --description "My API"
6675
"""
67-
try:
68-
target = TargetSpecifier.create(method, route, content_type)
69-
except ValueError as e:
70-
raise click.ClickException(str(e))
71-
72-
try:
73-
result = process_target(openapi_spec, target)
74-
except (OpenAPILoadError, TargetNotFoundError, AmbiguousContentTypeError) as e:
75-
raise click.ClickException(str(e))
76-
7776
flows_client = create_flows_client(ctx.globus_app)
7877

78+
target_content = json.loads(target.read_text())
7979
res = flows_client.create_registered_api(
8080
name=name,
8181
description=description,
82-
target=result.to_dict(),
82+
target=target_content,
83+
owners=list(owners),
84+
administrators=list(administrators),
85+
viewers=list(viewers),
8386
)
8487

85-
if format == "json":
86-
click.echo(json.dumps(res.data, indent=2))
87-
else:
88-
click.echo(f"ID: {res['id']}")
89-
click.echo(f"Name: {res['name']}")
90-
click.echo(f"Description: {res['description']}")
91-
click.echo(f"Created: {res['created_timestamp']}")
88+
echo_registered_api(res, format)

src/globus_registered_api/commands/api/show.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33
# Copyright 2025-2026 Globus <support@globus.org>
44
# SPDX-License-Identifier: Apache-2.0
55

6-
import json
7-
86
import click
97

108
from globus_registered_api.clients import create_flows_client
9+
from globus_registered_api.commands.api._common import echo_registered_api
1110
from globus_registered_api.context import CLIContext
1211
from globus_registered_api.context import with_cli_context
1312

@@ -24,11 +23,4 @@ def show_command(ctx: CLIContext, registered_api_id: str, format: str) -> None:
2423

2524
res = flows_client.get_registered_api(registered_api_id)
2625

27-
if format == "json":
28-
click.echo(json.dumps(res.data, indent=2))
29-
else:
30-
click.echo(f"ID: {res['id']}")
31-
click.echo(f"Name: {res['name']}")
32-
click.echo(f"Description: {res['description']}")
33-
click.echo(f"Created: {res['created_timestamp']}")
34-
click.echo(f"Updated: {res['updated_timestamp']}")
26+
echo_registered_api(res, format)

src/globus_registered_api/commands/api/update.py

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import click
1212

1313
from globus_registered_api.clients import create_flows_client
14+
from globus_registered_api.commands.api._common import echo_registered_api
1415
from globus_registered_api.context import CLIContext
1516
from globus_registered_api.context import with_cli_context
1617

@@ -51,9 +52,9 @@
5152
help="Clear all viewers (can only be set by owners and administrators)",
5253
)
5354
@click.option(
54-
"--target-file",
55+
"--target",
5556
type=click.Path(exists=True, dir_okay=False, path_type=pathlib.Path),
56-
help="Path to JSON file containing target definition",
57+
help="Filepath to a JSON object containing the target definition",
5758
)
5859
@click.option("--format", type=click.Choice(["json", "text"]), default="text")
5960
@with_cli_context
@@ -67,7 +68,7 @@ def update_command(
6768
viewers: tuple[str, ...],
6869
no_administrators: bool,
6970
no_viewers: bool,
70-
target_file: pathlib.Path | None,
71+
target: pathlib.Path | None,
7172
format: str,
7273
) -> None:
7374
"""
@@ -101,25 +102,14 @@ def update_command(
101102
request["viewers"] = []
102103
elif viewers:
103104
request["viewers"] = list(set(viewers))
104-
if target_file is not None:
105+
if target is not None:
105106
try:
106-
request["target"] = json.loads(target_file.read_text())
107+
request["target"] = json.loads(target.read_text())
107108
except json.JSONDecodeError as e:
108109
raise click.UsageError(f"Invalid JSON in target file: {e}")
109110
except UnicodeDecodeError as e:
110111
raise click.UsageError(f"Unable to read target file: {e}")
111112

112113
res = flows_client.update_registered_api(registered_api_id, **request)
113114

114-
if format == "json":
115-
click.echo(json.dumps(res.data, indent=2))
116-
else:
117-
click.echo(f"ID: {res['id']}")
118-
click.echo(f"Name: {res['name']}")
119-
click.echo(f"Description: {res['description']}")
120-
click.echo(f"Owners: {res['roles']['owners']}")
121-
click.echo(f"Administrators: {res['roles']['administrators']}")
122-
click.echo(f"Viewers: {res['roles']['viewers']}")
123-
click.echo(f"Created: {res['created_timestamp']}")
124-
if res.get("edited_timestamp"):
125-
click.echo(f"Edited: {res['edited_timestamp']}")
115+
echo_registered_api(res, format)

src/globus_registered_api/extended_flows_client.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,19 +128,30 @@ def create_registered_api(
128128
name: str,
129129
description: str,
130130
target: dict[str, t.Any],
131+
owners: list[str],
132+
administrators: list[str],
133+
viewers: list[str],
131134
) -> GlobusHTTPResponse:
132135
"""
133136
Create a new registered API.
134137
135138
:param name: Name for the registered API
136139
:param description: Description for the registered API
137140
:param target: Target definition dict (from OpenAPITarget.to_dict())
141+
:param owners: List of owner URNs.
142+
:param administrators: List of administrator URNs.
143+
:param viewers: List of viewer URNs.
138144
:return: Response containing the created registered API
139145
"""
140146
body: dict[str, t.Any] = {
141147
"name": name,
142148
"description": description,
143149
"target": target,
150+
"roles": {
151+
"owners": owners,
152+
"administrators": administrators,
153+
"viewers": viewers,
154+
},
144155
}
145156

146157
return self.post("/registered_apis", data=body)

0 commit comments

Comments
 (0)