entropy-data 0.3.6__tar.gz → 0.3.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. {entropy_data-0.3.6 → entropy_data-0.3.8}/CHANGELOG.md +9 -0
  2. {entropy_data-0.3.6 → entropy_data-0.3.8}/PKG-INFO +1 -1
  3. {entropy_data-0.3.6 → entropy_data-0.3.8}/README.md +1 -0
  4. {entropy_data-0.3.6 → entropy_data-0.3.8}/pyproject.toml +1 -1
  5. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/cli.py +2 -0
  6. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/access.py +21 -3
  7. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/api_keys.py +3 -4
  8. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/assets.py +11 -4
  9. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/connection.py +3 -4
  10. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/events.py +3 -4
  11. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/gitconnections.py +2 -6
  12. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/lineage.py +2 -6
  13. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/notification_channels.py +2 -6
  14. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/organization.py +3 -4
  15. entropy_data-0.3.8/src/entropy_data/commands/policies.py +79 -0
  16. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/search.py +3 -3
  17. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/usage.py +2 -6
  18. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/output.py +15 -4
  19. {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_access_request.py +36 -0
  20. entropy_data-0.3.8/tests/commands/test_policies.py +89 -0
  21. {entropy_data-0.3.6 → entropy_data-0.3.8}/.editorconfig +0 -0
  22. {entropy_data-0.3.6 → entropy_data-0.3.8}/.github/dependabot.yml +0 -0
  23. {entropy_data-0.3.6 → entropy_data-0.3.8}/.github/pull_request_template.md +0 -0
  24. {entropy_data-0.3.6 → entropy_data-0.3.8}/.github/workflows/ci.yaml +0 -0
  25. {entropy_data-0.3.6 → entropy_data-0.3.8}/.github/workflows/release.yaml +0 -0
  26. {entropy_data-0.3.6 → entropy_data-0.3.8}/.gitignore +0 -0
  27. {entropy_data-0.3.6 → entropy_data-0.3.8}/.pre-commit-config.yaml +0 -0
  28. {entropy_data-0.3.6 → entropy_data-0.3.8}/CLAUDE.md +0 -0
  29. {entropy_data-0.3.6 → entropy_data-0.3.8}/Dockerfile +0 -0
  30. {entropy_data-0.3.6 → entropy_data-0.3.8}/LICENSE +0 -0
  31. {entropy_data-0.3.6 → entropy_data-0.3.8}/release +0 -0
  32. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/__init__.py +0 -0
  33. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/__main__.py +0 -0
  34. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/client.py +0 -0
  35. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/__init__.py +0 -0
  36. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/certifications.py +0 -0
  37. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/connectors.py +0 -0
  38. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/costs.py +0 -0
  39. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/datacontracts.py +0 -0
  40. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/dataproducts.py +0 -0
  41. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/definitions.py +0 -0
  42. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/example_data.py +0 -0
  43. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/git_credentials.py +0 -0
  44. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/import_export.py +0 -0
  45. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/semantics.py +0 -0
  46. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/settings.py +0 -0
  47. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/sourcesystems.py +0 -0
  48. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/tags.py +0 -0
  49. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/teams.py +0 -0
  50. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/commands/test_results.py +0 -0
  51. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/config.py +0 -0
  52. {entropy_data-0.3.6 → entropy_data-0.3.8}/src/entropy_data/util.py +0 -0
  53. {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/__init__.py +0 -0
  54. {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/__init__.py +0 -0
  55. {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_api_keys.py +0 -0
  56. {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_assets.py +0 -0
  57. {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_connection.py +0 -0
  58. {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_connectors.py +0 -0
  59. {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_costs.py +0 -0
  60. {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_datacontracts.py +0 -0
  61. {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_dataproducts.py +0 -0
  62. {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_git_credentials.py +0 -0
  63. {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_gitconnections.py +0 -0
  64. {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_lineage.py +0 -0
  65. {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_notification_channels.py +0 -0
  66. {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_organization.py +0 -0
  67. {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_semantics.py +0 -0
  68. {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_settings.py +0 -0
  69. {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_tags.py +0 -0
  70. {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_teams.py +0 -0
  71. {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/commands/test_usage.py +0 -0
  72. {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/conftest.py +0 -0
  73. {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/test_client.py +0 -0
  74. {entropy_data-0.3.6 → entropy_data-0.3.8}/tests/test_config.py +0 -0
@@ -2,6 +2,15 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.3.8]
6
+
7
+ - Add `entropy-data policies list|get|put|delete` to manage policies. Wraps `/api/policies` and `/api/policies/{externalId}`.
8
+
9
+ ## [0.3.7]
10
+
11
+ - Add `yaml` as a third value for `--output` / `-o` alongside `table` and `json`. Useful when piping results into editors or files that expect YAML (e.g. data contracts).
12
+ - `entropy-data access list` now supports `--provider-dataproduct`, `--consumer-dataproduct`, and `--consumer-type` filters, passed through to the existing `providerDataProductId`, `consumerDataProductId`, and `consumerType` query parameters of `GET /api/access`.
13
+
5
14
  ## [0.3.6]
6
15
 
7
16
  - `entropy-data connection add` now sets the newly added connection as the default, overriding any previously set default.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: entropy-data
3
- Version: 0.3.6
3
+ Version: 0.3.8
4
4
  Summary: CLI for Entropy Data
5
5
  Project-URL: Homepage, https://entropy-data.com
6
6
  Project-URL: Documentation, https://docs.entropy-data.com
@@ -87,6 +87,7 @@ entropy-data [--version] [--connection NAME] [--output table|json] [--debug]
87
87
  sourcesystems list | get | put | delete
88
88
  definitions list | get | put | delete
89
89
  certifications list | get | put | delete
90
+ policies list | get | put | delete
90
91
  example-data list | get | put | delete
91
92
  test-results list | get | publish | delete
92
93
  costs list | add | delete
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "entropy-data"
3
- version = "0.3.6"
3
+ version = "0.3.8"
4
4
  description = "CLI for Entropy Data"
5
5
  requires-python = ">=3.11"
6
6
  license = "MIT"
@@ -105,6 +105,7 @@ from entropy_data.commands.example_data import example_data_app # noqa: E402
105
105
  from entropy_data.commands.import_export import import_app # noqa: E402
106
106
  from entropy_data.commands.lineage import lineage_app # noqa: E402
107
107
  from entropy_data.commands.organization import organization_app # noqa: E402
108
+ from entropy_data.commands.policies import policies_app # noqa: E402
108
109
  from entropy_data.commands.search import search_app # noqa: E402
109
110
  from entropy_data.commands.semantics import semantics_app # noqa: E402
110
111
  from entropy_data.commands.settings import settings_app # noqa: E402
@@ -122,6 +123,7 @@ app.add_typer(access_app, name="access", help="Manage access (data usage agreeme
122
123
  app.add_typer(sourcesystems_app, name="sourcesystems", help="Manage source systems.")
123
124
  app.add_typer(definitions_app, name="definitions", help="Manage definitions.")
124
125
  app.add_typer(certifications_app, name="certifications", help="Manage certifications.")
126
+ app.add_typer(policies_app, name="policies", help="Manage policies.")
125
127
  app.add_typer(example_data_app, name="example-data", help="Manage example data.")
126
128
  app.add_typer(test_results_app, name="test-results", help="Manage test results.")
127
129
  app.add_typer(costs_app, name="costs", help="Manage costs.")
@@ -17,6 +17,18 @@ RESOURCE_TYPE = "access"
17
17
  @access_app.command("list")
18
18
  def list_access(
19
19
  page: Annotated[int, typer.Option("--page", "-p", help="Page number (0-indexed).")] = 0,
20
+ provider_dataproduct: Annotated[
21
+ Optional[str],
22
+ typer.Option("--provider-dataproduct", help="Filter by provider data product ID."),
23
+ ] = None,
24
+ consumer_dataproduct: Annotated[
25
+ Optional[str],
26
+ typer.Option("--consumer-dataproduct", help="Filter by consumer data product ID."),
27
+ ] = None,
28
+ consumer_type: Annotated[
29
+ Optional[str],
30
+ typer.Option("--consumer-type", help="Filter by consumer type (e.g. team, user, dataProduct)."),
31
+ ] = None,
20
32
  output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
21
33
  ) -> None:
22
34
  """List all access agreements."""
@@ -25,7 +37,14 @@ def list_access(
25
37
  fmt = output or get_output_format()
26
38
  try:
27
39
  client = get_client()
28
- data, has_next = client.list_resources(RESOURCE_PATH, params={"p": page})
40
+ params: dict = {"p": page}
41
+ if provider_dataproduct:
42
+ params["providerDataProductId"] = provider_dataproduct
43
+ if consumer_dataproduct:
44
+ params["consumerDataProductId"] = consumer_dataproduct
45
+ if consumer_type:
46
+ params["consumerType"] = consumer_type
47
+ data, has_next = client.list_resources(RESOURCE_PATH, params=params)
29
48
  print_resource_list(data, RESOURCE_TYPE, fmt, has_next_page=has_next, page=page)
30
49
  except Exception as e:
31
50
  handle_error(e)
@@ -166,8 +185,7 @@ def request_access(
166
185
  consumer_count = sum(c is not None for c in (consumer_team, consumer_user, consumer_dataproduct))
167
186
  if consumer_count != 1:
168
187
  error_console.print(
169
- "[red]Error: provide exactly one of --consumer-team, --consumer-user, "
170
- "or --consumer-dataproduct.[/red]"
188
+ "[red]Error: provide exactly one of --consumer-team, --consumer-user, or --consumer-dataproduct.[/red]"
171
189
  )
172
190
  raise SystemExit(2)
173
191
 
@@ -1,11 +1,10 @@
1
1
  """API Keys commands."""
2
2
 
3
- import json
4
3
  from typing import Annotated, Optional
5
4
 
6
5
  import typer
7
6
 
8
- from entropy_data.output import OutputFormat, console, print_success
7
+ from entropy_data.output import OutputFormat, console, print_data, print_success
9
8
 
10
9
  api_keys_app = typer.Typer(no_args_is_help=True)
11
10
  RESOURCE_PATH = "api-keys"
@@ -38,8 +37,8 @@ def create_api_key(
38
37
 
39
38
  _raise_for_status(response)
40
39
  data = response.json()
41
- if fmt == OutputFormat.json:
42
- console.print_json(json.dumps(data))
40
+ if fmt != OutputFormat.table:
41
+ print_data(data, fmt)
43
42
  else:
44
43
  print_success(f"API key created: {data.get('organizationApiKeyId')}")
45
44
  key = data.get("key")
@@ -1,12 +1,19 @@
1
1
  """Assets commands."""
2
2
 
3
- import json
4
3
  from pathlib import Path
5
4
  from typing import Annotated, Optional
6
5
 
7
6
  import typer
8
7
 
9
- from entropy_data.output import OutputFormat, console, print_link, print_resource, print_resource_list, print_success
8
+ from entropy_data.output import (
9
+ OutputFormat,
10
+ console,
11
+ print_data,
12
+ print_link,
13
+ print_resource,
14
+ print_resource_list,
15
+ print_success,
16
+ )
10
17
  from entropy_data.util import read_body
11
18
 
12
19
  assets_app = typer.Typer(no_args_is_help=True)
@@ -101,8 +108,8 @@ def list_asset_tags(
101
108
  )
102
109
  _raise_for_status(response)
103
110
  data = response.json()
104
- if fmt == OutputFormat.json:
105
- console.print_json(json.dumps(data))
111
+ if fmt != OutputFormat.table:
112
+ print_data(data, fmt)
106
113
  else:
107
114
  for tag in data:
108
115
  console.print(tag)
@@ -1,13 +1,12 @@
1
1
  """Connection management commands."""
2
2
 
3
- import json
4
3
  from typing import Annotated, Optional
5
4
 
6
5
  import typer
7
6
  from rich.table import Table
8
7
 
9
8
  from entropy_data import config as cfg
10
- from entropy_data.output import OutputFormat, console, print_error, print_success
9
+ from entropy_data.output import OutputFormat, console, print_data, print_error, print_success
11
10
 
12
11
  connection_app = typer.Typer(no_args_is_help=True)
13
12
 
@@ -102,7 +101,7 @@ def get_connection(
102
101
  is_default = config.get("default_connection_name") == resolved_name
103
102
 
104
103
  fmt = output or get_output_format()
105
- if fmt == OutputFormat.json:
104
+ if fmt != OutputFormat.table:
106
105
  payload = {
107
106
  "name": resolved_name,
108
107
  "host": conn.get("host", cfg.DEFAULT_HOST),
@@ -110,7 +109,7 @@ def get_connection(
110
109
  "api_key": displayed_key,
111
110
  "default": is_default,
112
111
  }
113
- console.print_json(json.dumps(payload))
112
+ print_data(payload, fmt)
114
113
  return
115
114
 
116
115
  table = Table(show_header=False)
@@ -1,11 +1,10 @@
1
1
  """Events commands."""
2
2
 
3
- import json
4
3
  from typing import Annotated, Optional
5
4
 
6
5
  import typer
7
6
 
8
- from entropy_data.output import OutputFormat, console, print_resource_list
7
+ from entropy_data.output import OutputFormat, print_data, print_resource_list
9
8
 
10
9
  events_app = typer.Typer(no_args_is_help=True)
11
10
 
@@ -24,8 +23,8 @@ def poll_events(
24
23
  try:
25
24
  client = get_client()
26
25
  data = client.get_events(last_event_id=last_event_id)
27
- if fmt == OutputFormat.json:
28
- console.print_json(json.dumps(data))
26
+ if fmt != OutputFormat.table:
27
+ print_data(data, fmt)
29
28
  else:
30
29
  print_resource_list(data, "events", fmt)
31
30
  except Exception as e:
@@ -1,11 +1,10 @@
1
1
  """Git connection subcommands. Used as a sub-typer of dataproducts and datacontracts."""
2
2
 
3
- import json
4
3
  from typing import Annotated, Optional
5
4
 
6
5
  import typer
7
6
 
8
- from entropy_data.output import OutputFormat, print_link, print_success
7
+ from entropy_data.output import OutputFormat, print_data, print_link, print_success
9
8
 
10
9
  GIT_CONNECTION_TYPES = ("github", "gitlab", "bitbucket", "azuredevops")
11
10
 
@@ -31,10 +30,7 @@ def make_gitconnection_app(resource_path: str, resource_label: str) -> typer.Typ
31
30
  try:
32
31
  client = get_client()
33
32
  data = client.get_gitconnection(resource_path, id)
34
- if fmt == OutputFormat.json:
35
- print(json.dumps(data, indent=2))
36
- else:
37
- print(json.dumps(data, indent=2))
33
+ print_data(data, fmt)
38
34
  except Exception as e:
39
35
  handle_error(e)
40
36
 
@@ -1,12 +1,11 @@
1
1
  """Lineage commands (OpenLineage events)."""
2
2
 
3
- import json
4
3
  from pathlib import Path
5
4
  from typing import Annotated, Optional
6
5
 
7
6
  import typer
8
7
 
9
- from entropy_data.output import OutputFormat, console, print_resource_list, print_success
8
+ from entropy_data.output import OutputFormat, print_resource_list, print_success
10
9
  from entropy_data.util import read_body
11
10
 
12
11
  lineage_app = typer.Typer(no_args_is_help=True)
@@ -46,10 +45,7 @@ def list_lineage(
46
45
  params["dataProductId"] = data_product_id
47
46
  client = get_client()
48
47
  data, _ = client.list_resources(RESOURCE_PATH, params=params)
49
- if fmt == OutputFormat.json:
50
- console.print_json(json.dumps(data))
51
- else:
52
- print_resource_list(data, RESOURCE_TYPE, fmt)
48
+ print_resource_list(data, RESOURCE_TYPE, fmt)
53
49
  except Exception as e:
54
50
  handle_error(e)
55
51
 
@@ -1,12 +1,11 @@
1
1
  """Team notification channel subcommands."""
2
2
 
3
- import json
4
3
  from pathlib import Path
5
4
  from typing import Annotated, Optional
6
5
 
7
6
  import typer
8
7
 
9
- from entropy_data.output import OutputFormat, console, print_success
8
+ from entropy_data.output import OutputFormat, print_data, print_success
10
9
  from entropy_data.util import read_body
11
10
 
12
11
  notifications_app = typer.Typer(no_args_is_help=True)
@@ -34,10 +33,7 @@ def get_channel(
34
33
  response = client.session.get(_channel_url(client, team_id, channel_id), timeout=REQUEST_TIMEOUT)
35
34
  _raise_for_status(response)
36
35
  data = response.json()
37
- if fmt == OutputFormat.json:
38
- console.print_json(json.dumps(data))
39
- else:
40
- console.print_json(json.dumps(data))
36
+ print_data(data, fmt)
41
37
  except Exception as e:
42
38
  handle_error(e)
43
39
 
@@ -1,12 +1,11 @@
1
1
  """Organization commands."""
2
2
 
3
- import json
4
3
  from typing import Annotated, Optional
5
4
 
6
5
  import typer
7
6
  from rich.table import Table
8
7
 
9
- from entropy_data.output import OutputFormat, console, print_resource, print_resource_list
8
+ from entropy_data.output import OutputFormat, console, print_data, print_resource, print_resource_list
10
9
 
11
10
  organization_app = typer.Typer(no_args_is_help=True)
12
11
  members_app = typer.Typer(no_args_is_help=True)
@@ -29,8 +28,8 @@ def get_organization(
29
28
  )
30
29
  _raise_for_status(response)
31
30
  data = response.json()
32
- if fmt == OutputFormat.json:
33
- console.print_json(json.dumps(data))
31
+ if fmt != OutputFormat.table:
32
+ print_data(data, fmt)
34
33
  return
35
34
 
36
35
  table = Table(show_header=False)
@@ -0,0 +1,79 @@
1
+ """Policies commands."""
2
+
3
+ from pathlib import Path
4
+ from typing import Annotated, Optional
5
+
6
+ import typer
7
+
8
+ from entropy_data.output import OutputFormat, print_link, print_resource, print_resource_list, print_success
9
+ from entropy_data.util import read_body
10
+
11
+ policies_app = typer.Typer(no_args_is_help=True)
12
+ RESOURCE_PATH = "policies"
13
+ RESOURCE_TYPE = "policies"
14
+
15
+
16
+ @policies_app.command("list")
17
+ def list_policies(
18
+ output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
19
+ ) -> None:
20
+ """List all policies."""
21
+ from entropy_data.cli import get_client, get_output_format, handle_error
22
+
23
+ fmt = output or get_output_format()
24
+ try:
25
+ client = get_client()
26
+ data, has_next = client.list_resources(RESOURCE_PATH)
27
+ print_resource_list(data, RESOURCE_TYPE, fmt, has_next_page=has_next)
28
+ except Exception as e:
29
+ handle_error(e)
30
+
31
+
32
+ @policies_app.command("get")
33
+ def get_policy(
34
+ id: Annotated[str, typer.Argument(help="Policy ID.")],
35
+ output: Annotated[Optional[OutputFormat], typer.Option("--output", "-o", help="Output format.")] = None,
36
+ ) -> None:
37
+ """Get a policy by ID."""
38
+ from entropy_data.cli import get_client, get_output_format, handle_error
39
+
40
+ fmt = output or get_output_format()
41
+ try:
42
+ client = get_client()
43
+ data = client.get_resource(RESOURCE_PATH, id)
44
+ print_resource(data, RESOURCE_TYPE, fmt)
45
+ except Exception as e:
46
+ handle_error(e)
47
+
48
+
49
+ @policies_app.command("put")
50
+ def put_policy(
51
+ id: Annotated[str, typer.Argument(help="Policy ID.")],
52
+ file: Annotated[Path, typer.Option("--file", "-f", help="JSON or YAML file (use - for stdin).")] = ...,
53
+ ) -> None:
54
+ """Create or update a policy."""
55
+ from entropy_data.cli import get_client, handle_error
56
+
57
+ try:
58
+ body = read_body(file)
59
+ client = get_client()
60
+ location = client.put_resource(RESOURCE_PATH, id, body)
61
+ print_success(f"Policy '{id}' saved.")
62
+ print_link(location)
63
+ except Exception as e:
64
+ handle_error(e)
65
+
66
+
67
+ @policies_app.command("delete")
68
+ def delete_policy(
69
+ id: Annotated[str, typer.Argument(help="Policy ID.")],
70
+ ) -> None:
71
+ """Delete a policy."""
72
+ from entropy_data.cli import get_client, handle_error
73
+
74
+ try:
75
+ client = get_client()
76
+ client.delete_resource(RESOURCE_PATH, id)
77
+ print_success(f"Policy '{id}' deleted.")
78
+ except Exception as e:
79
+ handle_error(e)
@@ -5,7 +5,7 @@ from typing import Annotated, Optional
5
5
 
6
6
  import typer
7
7
 
8
- from entropy_data.output import OutputFormat, console
8
+ from entropy_data.output import OutputFormat, console, print_data
9
9
 
10
10
  search_app = typer.Typer(no_args_is_help=True)
11
11
 
@@ -29,8 +29,8 @@ def search_query(
29
29
  params["resourceType"] = resource_type
30
30
  data = client.search(query, **params)
31
31
 
32
- if fmt == OutputFormat.json:
33
- console.print_json(json.dumps(data))
32
+ if fmt != OutputFormat.table:
33
+ print_data(data, fmt)
34
34
  return
35
35
 
36
36
  # Table output for search results
@@ -1,12 +1,11 @@
1
1
  """Usage commands (OpenTelemetry traces)."""
2
2
 
3
- import json
4
3
  from pathlib import Path
5
4
  from typing import Annotated, Optional
6
5
 
7
6
  import typer
8
7
 
9
- from entropy_data.output import OutputFormat, console, print_resource_list, print_success
8
+ from entropy_data.output import OutputFormat, print_resource_list, print_success
10
9
  from entropy_data.util import read_body
11
10
 
12
11
  usage_app = typer.Typer(no_args_is_help=True)
@@ -41,10 +40,7 @@ def list_usage(
41
40
  params["dataContractId"] = data_contract_id
42
41
  client = get_client()
43
42
  data, _ = client.list_resources(RESOURCE_PATH, params=params)
44
- if fmt == OutputFormat.json:
45
- console.print_json(json.dumps(data))
46
- else:
47
- print_resource_list(data, RESOURCE_TYPE, fmt)
43
+ print_resource_list(data, RESOURCE_TYPE, fmt)
48
44
  except Exception as e:
49
45
  handle_error(e)
50
46
 
@@ -3,6 +3,7 @@
3
3
  import json
4
4
  from enum import Enum
5
5
 
6
+ import yaml
6
7
  from rich.console import Console
7
8
  from rich.table import Table
8
9
 
@@ -13,6 +14,15 @@ error_console = Console(stderr=True)
13
14
  class OutputFormat(str, Enum):
14
15
  table = "table"
15
16
  json = "json"
17
+ yaml = "yaml"
18
+
19
+
20
+ def print_data(data, fmt: OutputFormat) -> None:
21
+ """Print data structured as JSON or YAML."""
22
+ if fmt == OutputFormat.yaml:
23
+ console.print(yaml.safe_dump(data, sort_keys=False, default_flow_style=False), end="")
24
+ else:
25
+ console.print_json(json.dumps(data))
16
26
 
17
27
 
18
28
  # Column definitions per resource type: list of (header, dict_key)
@@ -32,6 +42,7 @@ RESOURCE_COLUMNS: dict[str, list[tuple[str, str]]] = {
32
42
  "sourcesystems": [("ID", "id"), ("Name", "name"), ("Owner", "owner")],
33
43
  "definitions": [("ID", "id"), ("Name", "title"), ("Owner", "owner")],
34
44
  "certifications": [("ID", "id"), ("Name", "name"), ("Rank", "rank"), ("Tag", "tag")],
45
+ "policies": [("ID", "id"), ("Name", "name"), ("Status", "status")],
35
46
  "example-data": [("ID", "id"), ("Data Product", "dataProductId"), ("Schema", "schemaName")],
36
47
  "test-results": [("ID", "id"), ("Data Contract", "dataContractId"), ("Result", "result")],
37
48
  "events": [("ID", "id"), ("Type", "type"), ("Subject", "subject"), ("Time", "time")],
@@ -96,8 +107,8 @@ def _get_nested(data: dict, key: str) -> str:
96
107
 
97
108
  def print_resource(data: dict, resource_type: str, fmt: OutputFormat) -> None:
98
109
  """Print a single resource."""
99
- if fmt == OutputFormat.json:
100
- console.print_json(json.dumps(data))
110
+ if fmt != OutputFormat.table:
111
+ print_data(data, fmt)
101
112
  return
102
113
 
103
114
  columns = RESOURCE_COLUMNS.get(resource_type, [])
@@ -120,8 +131,8 @@ def print_resource_list(
120
131
  title: str | None = None,
121
132
  ) -> None:
122
133
  """Print a list of resources."""
123
- if fmt == OutputFormat.json:
124
- console.print_json(json.dumps(data))
134
+ if fmt != OutputFormat.table:
135
+ print_data(data, fmt)
125
136
  return
126
137
 
127
138
  columns = RESOURCE_COLUMNS.get(resource_type, [])
@@ -116,3 +116,39 @@ def test_request_multiple_consumers(monkeypatch, tmp_path):
116
116
  ],
117
117
  )
118
118
  assert result.exit_code == 2
119
+
120
+
121
+ @responses.activate
122
+ def test_list_with_filters(monkeypatch, tmp_path):
123
+ _setup(monkeypatch, tmp_path)
124
+ responses.add(responses.GET, f"{BASE_URL}/api/access", json=[], status=200)
125
+ result = runner.invoke(
126
+ app,
127
+ [
128
+ "access",
129
+ "list",
130
+ "--provider-dataproduct",
131
+ "dp_provider",
132
+ "--consumer-dataproduct",
133
+ "dp_consumer",
134
+ "--consumer-type",
135
+ "team",
136
+ ],
137
+ )
138
+ assert result.exit_code == 0
139
+ qs = responses.calls[0].request.url.split("?", 1)[1]
140
+ assert "providerDataProductId=dp_provider" in qs
141
+ assert "consumerDataProductId=dp_consumer" in qs
142
+ assert "consumerType=team" in qs
143
+
144
+
145
+ @responses.activate
146
+ def test_list_without_filters(monkeypatch, tmp_path):
147
+ _setup(monkeypatch, tmp_path)
148
+ responses.add(responses.GET, f"{BASE_URL}/api/access", json=[], status=200)
149
+ result = runner.invoke(app, ["access", "list"])
150
+ assert result.exit_code == 0
151
+ qs = responses.calls[0].request.url.split("?", 1)[1]
152
+ assert "providerDataProductId" not in qs
153
+ assert "consumerDataProductId" not in qs
154
+ assert "consumerType" not in qs
@@ -0,0 +1,89 @@
1
+ """Tests for policies commands."""
2
+
3
+ import json
4
+
5
+ import responses
6
+ from typer.testing import CliRunner
7
+
8
+ import entropy_data.config as cfg
9
+ from entropy_data.cli import app
10
+
11
+ runner = CliRunner()
12
+ BASE_URL = "https://api.entropy-data.com"
13
+
14
+ POLICIES_LIST = [
15
+ {"id": "pol_pii_handling", "name": "PII Handling", "status": "Published", "content": "..."},
16
+ {"id": "pol_retention", "name": "Data Retention", "status": "Draft", "content": "..."},
17
+ ]
18
+
19
+
20
+ @responses.activate
21
+ def test_policies_list(monkeypatch, tmp_path):
22
+ monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
23
+ monkeypatch.setenv("ENTROPY_DATA_API_KEY", "test-key")
24
+ responses.add(responses.GET, f"{BASE_URL}/api/policies", json=POLICIES_LIST, status=200)
25
+ result = runner.invoke(app, ["policies", "list"])
26
+ assert result.exit_code == 0
27
+ assert "PII Handling" in result.output
28
+ assert "Data Retention" in result.output
29
+
30
+
31
+ @responses.activate
32
+ def test_policies_list_json(monkeypatch, tmp_path):
33
+ monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
34
+ monkeypatch.setenv("ENTROPY_DATA_API_KEY", "test-key")
35
+ responses.add(responses.GET, f"{BASE_URL}/api/policies", json=POLICIES_LIST, status=200)
36
+ result = runner.invoke(app, ["policies", "list", "--output", "json"])
37
+ assert result.exit_code == 0
38
+ data = json.loads(result.output)
39
+ assert len(data) == 2
40
+
41
+
42
+ @responses.activate
43
+ def test_policies_get(monkeypatch, tmp_path):
44
+ monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
45
+ monkeypatch.setenv("ENTROPY_DATA_API_KEY", "test-key")
46
+ responses.add(
47
+ responses.GET,
48
+ f"{BASE_URL}/api/policies/pol_pii_handling",
49
+ json=POLICIES_LIST[0],
50
+ status=200,
51
+ )
52
+ result = runner.invoke(app, ["policies", "get", "pol_pii_handling"])
53
+ assert result.exit_code == 0
54
+ assert "PII Handling" in result.output
55
+
56
+
57
+ @responses.activate
58
+ def test_policies_put(monkeypatch, tmp_path):
59
+ monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
60
+ monkeypatch.setenv("ENTROPY_DATA_API_KEY", "test-key")
61
+ responses.add(
62
+ responses.PUT,
63
+ f"{BASE_URL}/api/policies/pol_pii_handling",
64
+ status=200,
65
+ )
66
+ policy_file = tmp_path / "policy.json"
67
+ policy_file.write_text(json.dumps({"id": "pol_pii_handling", "name": "PII Handling", "status": "Draft"}))
68
+ result = runner.invoke(app, ["policies", "put", "pol_pii_handling", "--file", str(policy_file)])
69
+ assert result.exit_code == 0
70
+ assert "saved" in result.output
71
+
72
+
73
+ @responses.activate
74
+ def test_policies_delete(monkeypatch, tmp_path):
75
+ monkeypatch.setattr(cfg, "CONFIG_FILE", tmp_path / "config.toml")
76
+ monkeypatch.setenv("ENTROPY_DATA_API_KEY", "test-key")
77
+ responses.add(responses.DELETE, f"{BASE_URL}/api/policies/pol_pii_handling", status=200)
78
+ result = runner.invoke(app, ["policies", "delete", "pol_pii_handling"])
79
+ assert result.exit_code == 0
80
+ assert "deleted" in result.output
81
+
82
+
83
+ def test_policies_help():
84
+ result = runner.invoke(app, ["policies", "--help"])
85
+ assert result.exit_code == 0
86
+ assert "list" in result.output
87
+ assert "get" in result.output
88
+ assert "put" in result.output
89
+ assert "delete" in result.output
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes