bigeye-cli 0.9.0__tar.gz → 0.9.2__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.
- bigeye_cli-0.9.0/bigeye_cli/CLI_DOCS.md → bigeye_cli-0.9.2/PKG-INFO +96 -5
- bigeye_cli-0.9.0/PKG-INFO → bigeye_cli-0.9.2/bigeye_cli/CLI_DOCS.md +76 -25
- bigeye_cli-0.9.2/bigeye_cli/catalog/custom_repo_commands.py +342 -0
- bigeye_cli-0.9.2/bigeye_cli/catalog/custom_repo_options.py +35 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/functions.py +100 -1
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/lineage/lineage_commands.py +34 -3
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/pyproject.toml +2 -2
- bigeye_cli-0.9.0/bigeye_cli/catalog/custom_repo_commands.py +0 -275
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/LICENSE +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/__init__.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/__main__.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/__version__.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/bigconfig/__init__.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/bigconfig/bigconfig_commands.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/bigconfig/bigconfig_options.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/catalog/__init__.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/catalog/catalog_commands.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/collections/__init__.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/collections/collection_commands.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/configure/__init__.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/configure/configure_commands.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/deltas/__init__.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/deltas/deltas_commands.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/enums.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/exceptions/__init__.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/exceptions/exceptions.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/global_options.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/issues/__init__.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/issues/issue_commands.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/lineage/__init__.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/metric/__init__.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/metric/metric_commands.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/model/__init__.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/model/cicd_conf.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/model/github_report.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/model/vendor_report.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/workspace/__init__.py +0 -0
- {bigeye_cli-0.9.0 → bigeye_cli-0.9.2}/bigeye_cli/workspace/workspace_commands.py +0 -0
|
@@ -1,5 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: bigeye-cli
|
|
3
|
+
Version: 0.9.2
|
|
4
|
+
Summary: Bigeye CLI offers developer tools for maintaining your developer workspace.
|
|
5
|
+
Home-page: https://docs.bigeye.com/docs
|
|
6
|
+
License: Proprietary
|
|
7
|
+
Author: Bigeye
|
|
8
|
+
Author-email: support@bigeye.com
|
|
9
|
+
Requires-Python: >=3.11.0,<4.0.0
|
|
10
|
+
Classifier: License :: Other/Proprietary License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Requires-Dist: PyGithub (>=1.59.1,<2.0.0)
|
|
15
|
+
Requires-Dist: bigeye-sdk (>=0.11.17,<0.12.0)
|
|
16
|
+
Requires-Dist: click (>=8.1.8,<9.0.0)
|
|
17
|
+
Requires-Dist: rich (>=14.1.0,<15.0.0)
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
1
20
|
# **Bigeye CLI**
|
|
2
|
-
Version: 0.9.
|
|
21
|
+
Version: 0.9.2
|
|
3
22
|
## Installation
|
|
4
23
|
|
|
5
24
|
**Setting up your local/build environment**
|
|
@@ -1247,6 +1266,9 @@ $ bigeye catalog custom-repo [OPTIONS] COMMAND [ARGS]...
|
|
|
1247
1266
|
* `import`: Import a custom repository from an Excel...
|
|
1248
1267
|
* `export`: Export a custom repository to an Excel...
|
|
1249
1268
|
* `delete`: Entirely delete a custom repository,...
|
|
1269
|
+
* `contents`: List the Bigeye assets (schemas, tables,...
|
|
1270
|
+
* `add-contents`: Add existing Bigeye assets to a custom...
|
|
1271
|
+
* `remove-contents`: Remove assets from a custom node (e.g.
|
|
1250
1272
|
|
|
1251
1273
|
### `bigeye catalog custom-repo init`
|
|
1252
1274
|
|
|
@@ -1267,7 +1289,8 @@ $ bigeye catalog custom-repo init [OPTIONS]
|
|
|
1267
1289
|
|
|
1268
1290
|
### `bigeye catalog custom-repo import`
|
|
1269
1291
|
|
|
1270
|
-
Import a custom repository from an Excel workbook
|
|
1292
|
+
Import a custom repository from an Excel workbook, and/or add or update
|
|
1293
|
+
attributes on nodes already in Bigeye (by node id, catalog path, or FQN).
|
|
1271
1294
|
|
|
1272
1295
|
**Usage**:
|
|
1273
1296
|
|
|
@@ -1282,8 +1305,8 @@ $ bigeye catalog custom-repo import [OPTIONS]
|
|
|
1282
1305
|
* `-w, --workspace TEXT`: Name of the workspace configuration. [env var: BIGEYE_WORKSPACE; default: DEFAULT]
|
|
1283
1306
|
* `-f, --file TEXT`: Path to the custom repository .xlsx workbook. [required]
|
|
1284
1307
|
* `--cleanup-stale`: Delete nodes/edges present in Bigeye but missing from the sheet. Off by default.
|
|
1285
|
-
* `--skip-unresolved-edges / --no-skip-unresolved-edges`: Import the rest instead of aborting when some edge endpoints don't resolve to a Bigeye node (default: on). Skipped
|
|
1286
|
-
* `--unresolved-log TEXT`: Path for the unresolved-
|
|
1308
|
+
* `--skip-unresolved-edges / --no-skip-unresolved-edges`: Import the rest instead of aborting when some edge endpoints or attribute IDs don't resolve to a Bigeye node (default: on). Skipped references are written to the unresolved log. Pass --no-skip-unresolved-edges to abort on any unresolved reference. [default: skip-unresolved-edges]
|
|
1309
|
+
* `--unresolved-log TEXT`: Path for the unresolved-references log. Defaults to custom_repo_unresolved.log in the current working directory. [default: custom_repo_unresolved.log]
|
|
1287
1310
|
* `--help`: Show this message and exit.
|
|
1288
1311
|
|
|
1289
1312
|
### `bigeye catalog custom-repo export`
|
|
@@ -1325,6 +1348,72 @@ $ bigeye catalog custom-repo delete [OPTIONS]
|
|
|
1325
1348
|
* `-y, --yes`: Skip the confirmation prompt.
|
|
1326
1349
|
* `--help`: Show this message and exit.
|
|
1327
1350
|
|
|
1351
|
+
### `bigeye catalog custom-repo contents`
|
|
1352
|
+
|
|
1353
|
+
List the Bigeye assets (schemas, tables, files, ...) a custom node contains, such as the
|
|
1354
|
+
assets assigned to a CI code. Children in the repository's own hierarchy are not listed.
|
|
1355
|
+
|
|
1356
|
+
**Usage**:
|
|
1357
|
+
|
|
1358
|
+
```console
|
|
1359
|
+
$ bigeye catalog custom-repo contents [OPTIONS]
|
|
1360
|
+
```
|
|
1361
|
+
|
|
1362
|
+
**Options**:
|
|
1363
|
+
|
|
1364
|
+
* `-b, --bigeye_conf TEXT`: Bigeye Credential File Path [env var: BIGEYE_API_CRED_FILE]
|
|
1365
|
+
* `-c, --config_file TEXT`: Bigeye CLI Configuration File Path [env var: BIGEYE_API_CONFIG_FILE]
|
|
1366
|
+
* `-w, --workspace TEXT`: Name of the workspace configuration. [env var: BIGEYE_WORKSPACE; default: DEFAULT]
|
|
1367
|
+
* `--repo-name TEXT`: Display name of the custom repository. [required]
|
|
1368
|
+
* `--node TEXT`: The repository node to work on (e.g. a CI code): its ID from the workbook's Nodes sheet, or its display name if that is unique. [required]
|
|
1369
|
+
* `--help`: Show this message and exit.
|
|
1370
|
+
|
|
1371
|
+
### `bigeye catalog custom-repo add-contents`
|
|
1372
|
+
|
|
1373
|
+
Add existing Bigeye assets to a custom node (e.g. assign schemas or files to a CI code) by
|
|
1374
|
+
creating a containment edge to each. Assets keep their catalog location and lineage.
|
|
1375
|
+
|
|
1376
|
+
**Usage**:
|
|
1377
|
+
|
|
1378
|
+
```console
|
|
1379
|
+
$ bigeye catalog custom-repo add-contents [OPTIONS]
|
|
1380
|
+
```
|
|
1381
|
+
|
|
1382
|
+
**Options**:
|
|
1383
|
+
|
|
1384
|
+
* `-b, --bigeye_conf TEXT`: Bigeye Credential File Path [env var: BIGEYE_API_CRED_FILE]
|
|
1385
|
+
* `-c, --config_file TEXT`: Bigeye CLI Configuration File Path [env var: BIGEYE_API_CONFIG_FILE]
|
|
1386
|
+
* `-w, --workspace TEXT`: Name of the workspace configuration. [env var: BIGEYE_WORKSPACE; default: DEFAULT]
|
|
1387
|
+
* `--repo-name TEXT`: Display name of the custom repository. [required]
|
|
1388
|
+
* `--node TEXT`: The repository node to work on (e.g. a CI code): its ID from the workbook's Nodes sheet, or its display name if that is unique. [required]
|
|
1389
|
+
* `-a, --asset TEXT`: An asset to add or remove: a warehouse FQN (source.schema[.table[.column]]), a catalog path (dotted, or ' / '-separated as the catalog shows it), or id:<lineage node id>. Repeatable.
|
|
1390
|
+
* `--assets-file TEXT`: CSV or Excel file with one asset per row in the first column. A first row is skipped as a header only if it is one of (in any case): asset, assets, catalog path, exclude, exclude asset, exclude assets, excluded asset, excluded assets, fqn, fqns, node, node id, path. Any other first row is read as an asset.
|
|
1391
|
+
* `--dry-run`: Show what would change without writing.
|
|
1392
|
+
* `--help`: Show this message and exit.
|
|
1393
|
+
|
|
1394
|
+
### `bigeye catalog custom-repo remove-contents`
|
|
1395
|
+
|
|
1396
|
+
Remove assets from a custom node (e.g. unassign them from a CI code). Deletes only the
|
|
1397
|
+
containment edge between the node and each asset; the asset and its lineage are untouched.
|
|
1398
|
+
|
|
1399
|
+
**Usage**:
|
|
1400
|
+
|
|
1401
|
+
```console
|
|
1402
|
+
$ bigeye catalog custom-repo remove-contents [OPTIONS]
|
|
1403
|
+
```
|
|
1404
|
+
|
|
1405
|
+
**Options**:
|
|
1406
|
+
|
|
1407
|
+
* `-b, --bigeye_conf TEXT`: Bigeye Credential File Path [env var: BIGEYE_API_CRED_FILE]
|
|
1408
|
+
* `-c, --config_file TEXT`: Bigeye CLI Configuration File Path [env var: BIGEYE_API_CONFIG_FILE]
|
|
1409
|
+
* `-w, --workspace TEXT`: Name of the workspace configuration. [env var: BIGEYE_WORKSPACE; default: DEFAULT]
|
|
1410
|
+
* `--repo-name TEXT`: Display name of the custom repository. [required]
|
|
1411
|
+
* `--node TEXT`: The repository node to work on (e.g. a CI code): its ID from the workbook's Nodes sheet, or its display name if that is unique. [required]
|
|
1412
|
+
* `-a, --asset TEXT`: An asset to add or remove: a warehouse FQN (source.schema[.table[.column]]), a catalog path (dotted, or ' / '-separated as the catalog shows it), or id:<lineage node id>. Repeatable.
|
|
1413
|
+
* `--assets-file TEXT`: CSV or Excel file with one asset per row in the first column. A first row is skipped as a header only if it is one of (in any case): asset, assets, catalog path, exclude, exclude asset, exclude assets, excluded asset, excluded assets, fqn, fqns, node, node id, path. Any other first row is read as an asset.
|
|
1414
|
+
* `--dry-run`: Show what would change without writing.
|
|
1415
|
+
* `--help`: Show this message and exit.
|
|
1416
|
+
|
|
1328
1417
|
# `bigeye metric`
|
|
1329
1418
|
|
|
1330
1419
|
Metric Commands for Bigeye CLI
|
|
@@ -1949,7 +2038,7 @@ $ bigeye lineage export-graphs [OPTIONS]
|
|
|
1949
2038
|
* `-b, --bigeye_conf TEXT`: Bigeye Credential File Path [env var: BIGEYE_API_CRED_FILE]
|
|
1950
2039
|
* `-c, --config_file TEXT`: Bigeye CLI Configuration File Path [env var: BIGEYE_API_CONFIG_FILE]
|
|
1951
2040
|
* `-w, --workspace TEXT`: Name of the workspace configuration. [env var: BIGEYE_WORKSPACE; default: DEFAULT]
|
|
1952
|
-
* `--fqns-file TEXT`: Path to a CSV with a single column of fully-qualified catalog names. [required]
|
|
2041
|
+
* `--fqns-file TEXT`: Path to a CSV or Excel file with a single column of fully-qualified catalog names. [required]
|
|
1953
2042
|
* `--direction [upstream|downstream|both]`: Direction to traverse: upstream, downstream, or both. [default: both]
|
|
1954
2043
|
* `--content [all|nodes|edges|attributes]`: What each per-graph report contains: all, nodes, edges, or attributes. [default: all]
|
|
1955
2044
|
* `--include-etl / --no-include-etl`: Include or exclude ETL operator nodes. Defaults to including them. [default: include-etl]
|
|
@@ -1986,5 +2075,7 @@ $ bigeye lineage export-end-to-end [OPTIONS]
|
|
|
1986
2075
|
* `--depth INTEGER`: Per-window traversal depth. When set, each root's graph is fetched in bounded depth-hop windows, re-rooting at the lineage frontier until the lineage is exhausted end-to-end. This escapes the backend's per-request depth ceiling (lineage.max_depth) that truncates a single full fetch, and keeps each request small so very large graphs don't fail. Omit to request the graph in one call (the previous behavior, capped at the backend depth limit).
|
|
1987
2076
|
* `--concurrency INTEGER`: Max concurrent window requests when --depth is used. Lower it to reduce client memory and server load on wide graphs. Ignored without --depth. [default: 8]
|
|
1988
2077
|
* `-o, --output TEXT`: Output .csv path. Defaults to end_to_end_<root>.csv in the current directory.
|
|
2078
|
+
* `--exclude-file TEXT`: CSV or Excel file listing assets to leave out of the paths, one per row in the first column. Each is a catalog path or warehouse FQN, as written in the Hop_* columns, or id:<lineage node id>, and also excludes everything inside it. Paths are joined across excluded assets, so A <- X <- B with X excluded becomes A <- B. A first row is skipped as a header only if it is one of (in any case): asset, assets, catalog path, exclude, exclude asset, exclude assets, excluded asset, excluded assets, fqn, fqns, node, node id, path. Any other first row is read as an asset.
|
|
1989
2079
|
* `--help`: Show this message and exit.
|
|
1990
2080
|
|
|
2081
|
+
|
|
@@ -1,24 +1,5 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: bigeye-cli
|
|
3
|
-
Version: 0.9.0
|
|
4
|
-
Summary: Bigeye CLI offers developer tools for maintaining your developer workspace.
|
|
5
|
-
Home-page: https://docs.bigeye.com/docs
|
|
6
|
-
License: Proprietary
|
|
7
|
-
Author: Bigeye
|
|
8
|
-
Author-email: support@bigeye.com
|
|
9
|
-
Requires-Python: >=3.11.0,<4.0.0
|
|
10
|
-
Classifier: License :: Other/Proprietary License
|
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
-
Requires-Dist: PyGithub (>=1.59.1,<2.0.0)
|
|
15
|
-
Requires-Dist: bigeye-sdk (>=0.11.15,<0.12.0)
|
|
16
|
-
Requires-Dist: click (>=8.1.8,<9.0.0)
|
|
17
|
-
Requires-Dist: rich (>=14.1.0,<15.0.0)
|
|
18
|
-
Description-Content-Type: text/markdown
|
|
19
|
-
|
|
20
1
|
# **Bigeye CLI**
|
|
21
|
-
Version: 0.9.
|
|
2
|
+
Version: 0.9.2
|
|
22
3
|
## Installation
|
|
23
4
|
|
|
24
5
|
**Setting up your local/build environment**
|
|
@@ -1266,6 +1247,9 @@ $ bigeye catalog custom-repo [OPTIONS] COMMAND [ARGS]...
|
|
|
1266
1247
|
* `import`: Import a custom repository from an Excel...
|
|
1267
1248
|
* `export`: Export a custom repository to an Excel...
|
|
1268
1249
|
* `delete`: Entirely delete a custom repository,...
|
|
1250
|
+
* `contents`: List the Bigeye assets (schemas, tables,...
|
|
1251
|
+
* `add-contents`: Add existing Bigeye assets to a custom...
|
|
1252
|
+
* `remove-contents`: Remove assets from a custom node (e.g.
|
|
1269
1253
|
|
|
1270
1254
|
### `bigeye catalog custom-repo init`
|
|
1271
1255
|
|
|
@@ -1286,7 +1270,8 @@ $ bigeye catalog custom-repo init [OPTIONS]
|
|
|
1286
1270
|
|
|
1287
1271
|
### `bigeye catalog custom-repo import`
|
|
1288
1272
|
|
|
1289
|
-
Import a custom repository from an Excel workbook
|
|
1273
|
+
Import a custom repository from an Excel workbook, and/or add or update
|
|
1274
|
+
attributes on nodes already in Bigeye (by node id, catalog path, or FQN).
|
|
1290
1275
|
|
|
1291
1276
|
**Usage**:
|
|
1292
1277
|
|
|
@@ -1301,8 +1286,8 @@ $ bigeye catalog custom-repo import [OPTIONS]
|
|
|
1301
1286
|
* `-w, --workspace TEXT`: Name of the workspace configuration. [env var: BIGEYE_WORKSPACE; default: DEFAULT]
|
|
1302
1287
|
* `-f, --file TEXT`: Path to the custom repository .xlsx workbook. [required]
|
|
1303
1288
|
* `--cleanup-stale`: Delete nodes/edges present in Bigeye but missing from the sheet. Off by default.
|
|
1304
|
-
* `--skip-unresolved-edges / --no-skip-unresolved-edges`: Import the rest instead of aborting when some edge endpoints don't resolve to a Bigeye node (default: on). Skipped
|
|
1305
|
-
* `--unresolved-log TEXT`: Path for the unresolved-
|
|
1289
|
+
* `--skip-unresolved-edges / --no-skip-unresolved-edges`: Import the rest instead of aborting when some edge endpoints or attribute IDs don't resolve to a Bigeye node (default: on). Skipped references are written to the unresolved log. Pass --no-skip-unresolved-edges to abort on any unresolved reference. [default: skip-unresolved-edges]
|
|
1290
|
+
* `--unresolved-log TEXT`: Path for the unresolved-references log. Defaults to custom_repo_unresolved.log in the current working directory. [default: custom_repo_unresolved.log]
|
|
1306
1291
|
* `--help`: Show this message and exit.
|
|
1307
1292
|
|
|
1308
1293
|
### `bigeye catalog custom-repo export`
|
|
@@ -1344,6 +1329,72 @@ $ bigeye catalog custom-repo delete [OPTIONS]
|
|
|
1344
1329
|
* `-y, --yes`: Skip the confirmation prompt.
|
|
1345
1330
|
* `--help`: Show this message and exit.
|
|
1346
1331
|
|
|
1332
|
+
### `bigeye catalog custom-repo contents`
|
|
1333
|
+
|
|
1334
|
+
List the Bigeye assets (schemas, tables, files, ...) a custom node contains, such as the
|
|
1335
|
+
assets assigned to a CI code. Children in the repository's own hierarchy are not listed.
|
|
1336
|
+
|
|
1337
|
+
**Usage**:
|
|
1338
|
+
|
|
1339
|
+
```console
|
|
1340
|
+
$ bigeye catalog custom-repo contents [OPTIONS]
|
|
1341
|
+
```
|
|
1342
|
+
|
|
1343
|
+
**Options**:
|
|
1344
|
+
|
|
1345
|
+
* `-b, --bigeye_conf TEXT`: Bigeye Credential File Path [env var: BIGEYE_API_CRED_FILE]
|
|
1346
|
+
* `-c, --config_file TEXT`: Bigeye CLI Configuration File Path [env var: BIGEYE_API_CONFIG_FILE]
|
|
1347
|
+
* `-w, --workspace TEXT`: Name of the workspace configuration. [env var: BIGEYE_WORKSPACE; default: DEFAULT]
|
|
1348
|
+
* `--repo-name TEXT`: Display name of the custom repository. [required]
|
|
1349
|
+
* `--node TEXT`: The repository node to work on (e.g. a CI code): its ID from the workbook's Nodes sheet, or its display name if that is unique. [required]
|
|
1350
|
+
* `--help`: Show this message and exit.
|
|
1351
|
+
|
|
1352
|
+
### `bigeye catalog custom-repo add-contents`
|
|
1353
|
+
|
|
1354
|
+
Add existing Bigeye assets to a custom node (e.g. assign schemas or files to a CI code) by
|
|
1355
|
+
creating a containment edge to each. Assets keep their catalog location and lineage.
|
|
1356
|
+
|
|
1357
|
+
**Usage**:
|
|
1358
|
+
|
|
1359
|
+
```console
|
|
1360
|
+
$ bigeye catalog custom-repo add-contents [OPTIONS]
|
|
1361
|
+
```
|
|
1362
|
+
|
|
1363
|
+
**Options**:
|
|
1364
|
+
|
|
1365
|
+
* `-b, --bigeye_conf TEXT`: Bigeye Credential File Path [env var: BIGEYE_API_CRED_FILE]
|
|
1366
|
+
* `-c, --config_file TEXT`: Bigeye CLI Configuration File Path [env var: BIGEYE_API_CONFIG_FILE]
|
|
1367
|
+
* `-w, --workspace TEXT`: Name of the workspace configuration. [env var: BIGEYE_WORKSPACE; default: DEFAULT]
|
|
1368
|
+
* `--repo-name TEXT`: Display name of the custom repository. [required]
|
|
1369
|
+
* `--node TEXT`: The repository node to work on (e.g. a CI code): its ID from the workbook's Nodes sheet, or its display name if that is unique. [required]
|
|
1370
|
+
* `-a, --asset TEXT`: An asset to add or remove: a warehouse FQN (source.schema[.table[.column]]), a catalog path (dotted, or ' / '-separated as the catalog shows it), or id:<lineage node id>. Repeatable.
|
|
1371
|
+
* `--assets-file TEXT`: CSV or Excel file with one asset per row in the first column. A first row is skipped as a header only if it is one of (in any case): asset, assets, catalog path, exclude, exclude asset, exclude assets, excluded asset, excluded assets, fqn, fqns, node, node id, path. Any other first row is read as an asset.
|
|
1372
|
+
* `--dry-run`: Show what would change without writing.
|
|
1373
|
+
* `--help`: Show this message and exit.
|
|
1374
|
+
|
|
1375
|
+
### `bigeye catalog custom-repo remove-contents`
|
|
1376
|
+
|
|
1377
|
+
Remove assets from a custom node (e.g. unassign them from a CI code). Deletes only the
|
|
1378
|
+
containment edge between the node and each asset; the asset and its lineage are untouched.
|
|
1379
|
+
|
|
1380
|
+
**Usage**:
|
|
1381
|
+
|
|
1382
|
+
```console
|
|
1383
|
+
$ bigeye catalog custom-repo remove-contents [OPTIONS]
|
|
1384
|
+
```
|
|
1385
|
+
|
|
1386
|
+
**Options**:
|
|
1387
|
+
|
|
1388
|
+
* `-b, --bigeye_conf TEXT`: Bigeye Credential File Path [env var: BIGEYE_API_CRED_FILE]
|
|
1389
|
+
* `-c, --config_file TEXT`: Bigeye CLI Configuration File Path [env var: BIGEYE_API_CONFIG_FILE]
|
|
1390
|
+
* `-w, --workspace TEXT`: Name of the workspace configuration. [env var: BIGEYE_WORKSPACE; default: DEFAULT]
|
|
1391
|
+
* `--repo-name TEXT`: Display name of the custom repository. [required]
|
|
1392
|
+
* `--node TEXT`: The repository node to work on (e.g. a CI code): its ID from the workbook's Nodes sheet, or its display name if that is unique. [required]
|
|
1393
|
+
* `-a, --asset TEXT`: An asset to add or remove: a warehouse FQN (source.schema[.table[.column]]), a catalog path (dotted, or ' / '-separated as the catalog shows it), or id:<lineage node id>. Repeatable.
|
|
1394
|
+
* `--assets-file TEXT`: CSV or Excel file with one asset per row in the first column. A first row is skipped as a header only if it is one of (in any case): asset, assets, catalog path, exclude, exclude asset, exclude assets, excluded asset, excluded assets, fqn, fqns, node, node id, path. Any other first row is read as an asset.
|
|
1395
|
+
* `--dry-run`: Show what would change without writing.
|
|
1396
|
+
* `--help`: Show this message and exit.
|
|
1397
|
+
|
|
1347
1398
|
# `bigeye metric`
|
|
1348
1399
|
|
|
1349
1400
|
Metric Commands for Bigeye CLI
|
|
@@ -1968,7 +2019,7 @@ $ bigeye lineage export-graphs [OPTIONS]
|
|
|
1968
2019
|
* `-b, --bigeye_conf TEXT`: Bigeye Credential File Path [env var: BIGEYE_API_CRED_FILE]
|
|
1969
2020
|
* `-c, --config_file TEXT`: Bigeye CLI Configuration File Path [env var: BIGEYE_API_CONFIG_FILE]
|
|
1970
2021
|
* `-w, --workspace TEXT`: Name of the workspace configuration. [env var: BIGEYE_WORKSPACE; default: DEFAULT]
|
|
1971
|
-
* `--fqns-file TEXT`: Path to a CSV with a single column of fully-qualified catalog names. [required]
|
|
2022
|
+
* `--fqns-file TEXT`: Path to a CSV or Excel file with a single column of fully-qualified catalog names. [required]
|
|
1972
2023
|
* `--direction [upstream|downstream|both]`: Direction to traverse: upstream, downstream, or both. [default: both]
|
|
1973
2024
|
* `--content [all|nodes|edges|attributes]`: What each per-graph report contains: all, nodes, edges, or attributes. [default: all]
|
|
1974
2025
|
* `--include-etl / --no-include-etl`: Include or exclude ETL operator nodes. Defaults to including them. [default: include-etl]
|
|
@@ -2005,6 +2056,6 @@ $ bigeye lineage export-end-to-end [OPTIONS]
|
|
|
2005
2056
|
* `--depth INTEGER`: Per-window traversal depth. When set, each root's graph is fetched in bounded depth-hop windows, re-rooting at the lineage frontier until the lineage is exhausted end-to-end. This escapes the backend's per-request depth ceiling (lineage.max_depth) that truncates a single full fetch, and keeps each request small so very large graphs don't fail. Omit to request the graph in one call (the previous behavior, capped at the backend depth limit).
|
|
2006
2057
|
* `--concurrency INTEGER`: Max concurrent window requests when --depth is used. Lower it to reduce client memory and server load on wide graphs. Ignored without --depth. [default: 8]
|
|
2007
2058
|
* `-o, --output TEXT`: Output .csv path. Defaults to end_to_end_<root>.csv in the current directory.
|
|
2059
|
+
* `--exclude-file TEXT`: CSV or Excel file listing assets to leave out of the paths, one per row in the first column. Each is a catalog path or warehouse FQN, as written in the Hop_* columns, or id:<lineage node id>, and also excludes everything inside it. Paths are joined across excluded assets, so A <- X <- B with X excluded becomes A <- B. A first row is skipped as a header only if it is one of (in any case): asset, assets, catalog path, exclude, exclude asset, exclude assets, excluded asset, excluded assets, fqn, fqns, node, node id, path. Any other first row is read as an asset.
|
|
2008
2060
|
* `--help`: Show this message and exit.
|
|
2009
2061
|
|
|
2010
|
-
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from bigeye_cli import global_options
|
|
8
|
+
from bigeye_cli.catalog import custom_repo_options
|
|
9
|
+
from bigeye_cli.functions import change_node_contents, cli_client_factory, contained_asset_path, contained_asset_type
|
|
10
|
+
from bigeye_sdk.controller.custom_repository_controller import CustomRepositoryController
|
|
11
|
+
from bigeye_sdk.log import get_logger
|
|
12
|
+
from bigeye_sdk.model.custom_repository_excel import (
|
|
13
|
+
CustomRepoWorkbook,
|
|
14
|
+
WorkbookValidationError,
|
|
15
|
+
build_config,
|
|
16
|
+
collect_node_attribute_updates,
|
|
17
|
+
find_resolved_conflicts,
|
|
18
|
+
read_workbook,
|
|
19
|
+
validate_workbook,
|
|
20
|
+
workbook_defines_repository,
|
|
21
|
+
write_workbook,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
log = get_logger(__file__)
|
|
25
|
+
|
|
26
|
+
app = typer.Typer(
|
|
27
|
+
no_args_is_help=True,
|
|
28
|
+
help="Import and export custom repositories as Excel (.xlsx) workbooks.",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _print_errors(header: str, errors: List[WorkbookValidationError]) -> None:
|
|
33
|
+
typer.echo(f"{header} with {len(errors)} error(s):", err=True)
|
|
34
|
+
for error in errors:
|
|
35
|
+
typer.echo(f" {error.format()}", err=True)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _write_unresolved_log(
|
|
39
|
+
log_path: str, repo_name: str, unresolved: List[WorkbookValidationError]
|
|
40
|
+
) -> bool:
|
|
41
|
+
"""Write a log file naming the edge endpoints and attribute IDs that could not be
|
|
42
|
+
resolved to a unique Bigeye node — either no match (the object or node id does
|
|
43
|
+
not exist) or more than one match (ambiguous). Best-effort: returns False and
|
|
44
|
+
warns if the file cannot be written (the same references are also printed to
|
|
45
|
+
stderr).
|
|
46
|
+
|
|
47
|
+
Each reference is a catalog path 'Repo.schema.table.column', a warehouse FQN,
|
|
48
|
+
or a lineage node id.
|
|
49
|
+
"""
|
|
50
|
+
lines = [
|
|
51
|
+
"Custom repository import — unresolved references",
|
|
52
|
+
f"Repository: {repo_name or '(none — attributes only)'}",
|
|
53
|
+
f"Generated: {datetime.now().isoformat(timespec='seconds')}",
|
|
54
|
+
f"Count: {len(unresolved)}",
|
|
55
|
+
"",
|
|
56
|
+
"These references could not be resolved to a unique Bigeye node (no match, or",
|
|
57
|
+
"more than one), so the edges and attributes referencing them were skipped.",
|
|
58
|
+
"Each line below states the sheet, the reason, and the reference value.",
|
|
59
|
+
"",
|
|
60
|
+
]
|
|
61
|
+
lines.extend(error.format() for error in unresolved)
|
|
62
|
+
try:
|
|
63
|
+
with open(log_path, "w") as f:
|
|
64
|
+
f.write("\n".join(lines) + "\n")
|
|
65
|
+
return True
|
|
66
|
+
except OSError as e:
|
|
67
|
+
typer.echo(f"Warning: could not write unresolved log to '{log_path}': {e}", err=True)
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@app.command(name="init")
|
|
72
|
+
def init_repo(
|
|
73
|
+
output: str = typer.Option(
|
|
74
|
+
"custom_repo_template.xlsx",
|
|
75
|
+
"--output",
|
|
76
|
+
"-o",
|
|
77
|
+
help="Output .xlsx path for the blank template.",
|
|
78
|
+
),
|
|
79
|
+
force: bool = typer.Option(
|
|
80
|
+
False,
|
|
81
|
+
"--force",
|
|
82
|
+
help="Overwrite the output file if it already exists.",
|
|
83
|
+
),
|
|
84
|
+
):
|
|
85
|
+
"""Generate a blank custom repository workbook (branded, with the Instructions
|
|
86
|
+
sheet and dropdowns) for you to populate, then import with `custom-repo import`."""
|
|
87
|
+
if os.path.exists(output) and not force:
|
|
88
|
+
typer.echo(
|
|
89
|
+
f"'{output}' already exists. Choose a different --output or pass --force to overwrite.",
|
|
90
|
+
err=True,
|
|
91
|
+
)
|
|
92
|
+
raise typer.Exit(code=1)
|
|
93
|
+
|
|
94
|
+
write_workbook(CustomRepoWorkbook(repo_name=""), output)
|
|
95
|
+
log.info(f"Wrote blank custom repository template to {output}")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@app.command(name="import")
|
|
99
|
+
def import_repo(
|
|
100
|
+
bigeye_conf: str = global_options.bigeye_conf,
|
|
101
|
+
config_file: str = global_options.config_file,
|
|
102
|
+
workspace: str = global_options.workspace,
|
|
103
|
+
file: str = typer.Option(
|
|
104
|
+
..., "--file", "-f", help="Path to the custom repository .xlsx workbook."
|
|
105
|
+
),
|
|
106
|
+
cleanup_stale: bool = typer.Option(
|
|
107
|
+
False,
|
|
108
|
+
"--cleanup-stale",
|
|
109
|
+
help="Delete nodes/edges present in Bigeye but missing from the sheet. Off by default.",
|
|
110
|
+
),
|
|
111
|
+
skip_unresolved_edges: bool = typer.Option(
|
|
112
|
+
True,
|
|
113
|
+
"--skip-unresolved-edges/--no-skip-unresolved-edges",
|
|
114
|
+
help="Import the rest instead of aborting when some edge endpoints or attribute "
|
|
115
|
+
"IDs don't resolve to a Bigeye node (default: on). Skipped references are written "
|
|
116
|
+
"to the unresolved log. Pass --no-skip-unresolved-edges to abort on any "
|
|
117
|
+
"unresolved reference.",
|
|
118
|
+
),
|
|
119
|
+
unresolved_log: str = typer.Option(
|
|
120
|
+
"custom_repo_unresolved.log",
|
|
121
|
+
"--unresolved-log",
|
|
122
|
+
help="Path for the unresolved-references log. Defaults to "
|
|
123
|
+
"custom_repo_unresolved.log in the current working directory.",
|
|
124
|
+
),
|
|
125
|
+
):
|
|
126
|
+
"""Import a custom repository from an Excel workbook, and/or add or update
|
|
127
|
+
attributes on nodes already in Bigeye (by node id, catalog path, or FQN)."""
|
|
128
|
+
client = cli_client_factory(bigeye_conf, config_file, workspace)
|
|
129
|
+
controller = CustomRepositoryController(client)
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
wb = read_workbook(file)
|
|
133
|
+
except (FileNotFoundError, ValueError) as e:
|
|
134
|
+
typer.echo(f"Could not read workbook '{file}': {e}", err=True)
|
|
135
|
+
raise typer.Exit(code=1)
|
|
136
|
+
|
|
137
|
+
validation_errors = validate_workbook(wb)
|
|
138
|
+
if validation_errors:
|
|
139
|
+
_print_errors("Import validation failed", validation_errors)
|
|
140
|
+
raise typer.Exit(code=1)
|
|
141
|
+
|
|
142
|
+
defines_repository = workbook_defines_repository(wb)
|
|
143
|
+
if cleanup_stale and not defines_repository:
|
|
144
|
+
typer.echo(
|
|
145
|
+
"--cleanup-stale needs a repository (Hierarchy and Nodes sheets); this workbook "
|
|
146
|
+
"only updates attributes on existing nodes.",
|
|
147
|
+
err=True,
|
|
148
|
+
)
|
|
149
|
+
raise typer.Exit(code=1)
|
|
150
|
+
|
|
151
|
+
path_node_ids, unresolved, lookup_failures = controller.resolve_workbook_references(wb)
|
|
152
|
+
conflicts = find_resolved_conflicts(wb, path_node_ids)
|
|
153
|
+
if conflicts:
|
|
154
|
+
_print_errors("Import validation failed", conflicts)
|
|
155
|
+
raise typer.Exit(code=1)
|
|
156
|
+
if unresolved:
|
|
157
|
+
log_written = _write_unresolved_log(unresolved_log, wb.repo_name, unresolved)
|
|
158
|
+
_print_errors("Could not resolve all catalog path references", unresolved)
|
|
159
|
+
log_hint = f" (see '{unresolved_log}')" if log_written else ""
|
|
160
|
+
if not skip_unresolved_edges:
|
|
161
|
+
typer.echo(
|
|
162
|
+
f"{len(unresolved)} unresolved reference(s){log_hint}. "
|
|
163
|
+
f"Re-run with --skip-unresolved-edges to import the rest.",
|
|
164
|
+
err=True,
|
|
165
|
+
)
|
|
166
|
+
raise typer.Exit(code=1)
|
|
167
|
+
typer.echo(
|
|
168
|
+
f"Skipping {len(unresolved)} unresolvable reference(s){log_hint} "
|
|
169
|
+
f"and importing the rest.",
|
|
170
|
+
err=True,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
if defines_repository:
|
|
174
|
+
config = build_config(
|
|
175
|
+
wb,
|
|
176
|
+
path_node_ids=path_node_ids,
|
|
177
|
+
cleanup_stale=cleanup_stale,
|
|
178
|
+
drop_unresolved_edges=skip_unresolved_edges,
|
|
179
|
+
)
|
|
180
|
+
result = controller.sync_from_config(config, wait_for_completion=True)
|
|
181
|
+
|
|
182
|
+
log.info(
|
|
183
|
+
f"Sync complete for '{wb.repo_name}': {result.nodes_synced} nodes, "
|
|
184
|
+
f"{result.edges_synced} edges submitted."
|
|
185
|
+
)
|
|
186
|
+
if result.edges_dropped:
|
|
187
|
+
log.warning(f"{result.edges_dropped} edge(s) dropped during sync:")
|
|
188
|
+
for reason in result.dropped_edge_reasons:
|
|
189
|
+
log.warning(f" - {reason}")
|
|
190
|
+
|
|
191
|
+
attribute_updates = collect_node_attribute_updates(wb, path_node_ids)
|
|
192
|
+
attribute_failures = 0
|
|
193
|
+
if attribute_updates:
|
|
194
|
+
attribute_result = controller.apply_node_attributes(attribute_updates)
|
|
195
|
+
log.info(
|
|
196
|
+
f"Attributes updated on {attribute_result.nodes_updated} existing node(s) "
|
|
197
|
+
f"({attribute_result.attributes_changed} attribute value(s) changed); "
|
|
198
|
+
f"{attribute_result.nodes_unchanged} already up to date."
|
|
199
|
+
)
|
|
200
|
+
attribute_failures = len(attribute_result.failures)
|
|
201
|
+
if attribute_failures:
|
|
202
|
+
typer.echo(f"Could not update attributes on {attribute_failures} node(s):", err=True)
|
|
203
|
+
for node_id, reason in sorted(attribute_result.failures.items()):
|
|
204
|
+
typer.echo(f" node_id {node_id}: {reason}", err=True)
|
|
205
|
+
|
|
206
|
+
if lookup_failures:
|
|
207
|
+
typer.echo(
|
|
208
|
+
f"{lookup_failures} reference(s) were skipped because their node could not be read; "
|
|
209
|
+
f"re-run the import to retry them.",
|
|
210
|
+
err=True,
|
|
211
|
+
)
|
|
212
|
+
if attribute_failures or lookup_failures:
|
|
213
|
+
raise typer.Exit(code=1)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@app.command(name="export")
|
|
217
|
+
def export_repo(
|
|
218
|
+
bigeye_conf: str = global_options.bigeye_conf,
|
|
219
|
+
config_file: str = global_options.config_file,
|
|
220
|
+
workspace: str = global_options.workspace,
|
|
221
|
+
repo_name: str = typer.Option(
|
|
222
|
+
..., "--repo-name", help="Display name of the custom repository to export."
|
|
223
|
+
),
|
|
224
|
+
output: Optional[str] = typer.Option(
|
|
225
|
+
None,
|
|
226
|
+
"--output",
|
|
227
|
+
"-o",
|
|
228
|
+
help="Output .xlsx path. Defaults to <repo-name>.xlsx in the current directory.",
|
|
229
|
+
),
|
|
230
|
+
):
|
|
231
|
+
"""Export a custom repository to an Excel workbook."""
|
|
232
|
+
client = cli_client_factory(bigeye_conf, config_file, workspace)
|
|
233
|
+
controller = CustomRepositoryController(client)
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
wb = controller.export_repository(repo_name)
|
|
237
|
+
except ValueError as e:
|
|
238
|
+
typer.echo(str(e), err=True)
|
|
239
|
+
raise typer.Exit(code=1)
|
|
240
|
+
|
|
241
|
+
output_path = output or f"{repo_name}.xlsx"
|
|
242
|
+
write_workbook(wb, output_path)
|
|
243
|
+
log.info(
|
|
244
|
+
f"Exported repository '{repo_name}' to {output_path} "
|
|
245
|
+
f"({len(wb.nodes)} nodes, {len(wb.edges)} edges)."
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@app.command(name="delete")
|
|
250
|
+
def delete_repo(
|
|
251
|
+
bigeye_conf: str = global_options.bigeye_conf,
|
|
252
|
+
config_file: str = global_options.config_file,
|
|
253
|
+
workspace: str = global_options.workspace,
|
|
254
|
+
repo_name: str = typer.Option(
|
|
255
|
+
..., "--repo-name", help="Display name of the custom repository to delete."
|
|
256
|
+
),
|
|
257
|
+
yes: bool = typer.Option(
|
|
258
|
+
False,
|
|
259
|
+
"--yes",
|
|
260
|
+
"-y",
|
|
261
|
+
help="Skip the confirmation prompt.",
|
|
262
|
+
),
|
|
263
|
+
):
|
|
264
|
+
"""Entirely delete a custom repository, including its nodes, edges, node types,
|
|
265
|
+
and integration type."""
|
|
266
|
+
client = cli_client_factory(bigeye_conf, config_file, workspace)
|
|
267
|
+
controller = CustomRepositoryController(client)
|
|
268
|
+
|
|
269
|
+
if not yes:
|
|
270
|
+
typer.confirm(
|
|
271
|
+
f"Permanently delete custom repository '{repo_name}' and all its nodes, "
|
|
272
|
+
f"edges, node types, and integration type?",
|
|
273
|
+
abort=True,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
try:
|
|
277
|
+
result = controller.delete_repository(repo_name)
|
|
278
|
+
except ValueError as e:
|
|
279
|
+
typer.echo(str(e), err=True)
|
|
280
|
+
raise typer.Exit(code=1)
|
|
281
|
+
|
|
282
|
+
summary = f"Deleted custom repository '{result.repository_name}' (id {result.repository_id})"
|
|
283
|
+
if result.integration_type_deleted:
|
|
284
|
+
summary += f", its integration type, and {result.node_types_deleted} node type(s)"
|
|
285
|
+
else:
|
|
286
|
+
summary += " (integration type retained — shared by another repository)"
|
|
287
|
+
log.info(summary + ".")
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@app.command(name="contents")
|
|
291
|
+
def node_contents(
|
|
292
|
+
bigeye_conf: str = global_options.bigeye_conf,
|
|
293
|
+
config_file: str = global_options.config_file,
|
|
294
|
+
workspace: str = global_options.workspace,
|
|
295
|
+
repo_name: str = custom_repo_options.repo_name,
|
|
296
|
+
node: str = custom_repo_options.node,
|
|
297
|
+
):
|
|
298
|
+
"""List the Bigeye assets (schemas, tables, files, ...) a custom node contains, such as the
|
|
299
|
+
assets assigned to a CI code. Children in the repository's own hierarchy are not listed."""
|
|
300
|
+
client = cli_client_factory(bigeye_conf, config_file, workspace)
|
|
301
|
+
controller = CustomRepositoryController(client)
|
|
302
|
+
try:
|
|
303
|
+
data_node, contents = controller.list_node_contents(repo_name, node)
|
|
304
|
+
except ValueError as e:
|
|
305
|
+
typer.echo(str(e), err=True)
|
|
306
|
+
raise typer.Exit(code=1)
|
|
307
|
+
|
|
308
|
+
typer.echo(f"{data_node.node_name} (node {data_node.id}) contains {len(contents)} asset(s):")
|
|
309
|
+
for child in sorted(contents, key=contained_asset_path):
|
|
310
|
+
typer.echo(f" id:{child.id}\t{contained_asset_type(child)}\t{contained_asset_path(child)}")
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@app.command(name="add-contents")
|
|
314
|
+
def add_node_contents(
|
|
315
|
+
bigeye_conf: str = global_options.bigeye_conf,
|
|
316
|
+
config_file: str = global_options.config_file,
|
|
317
|
+
workspace: str = global_options.workspace,
|
|
318
|
+
repo_name: str = custom_repo_options.repo_name,
|
|
319
|
+
node: str = custom_repo_options.node,
|
|
320
|
+
asset: Optional[List[str]] = custom_repo_options.asset,
|
|
321
|
+
assets_file: Optional[str] = custom_repo_options.assets_file,
|
|
322
|
+
dry_run: bool = custom_repo_options.dry_run,
|
|
323
|
+
):
|
|
324
|
+
"""Add existing Bigeye assets to a custom node (e.g. assign schemas or files to a CI code) by
|
|
325
|
+
creating a containment edge to each. Assets keep their catalog location and lineage."""
|
|
326
|
+
change_node_contents(bigeye_conf, config_file, workspace, repo_name, node, asset, assets_file, dry_run, add=True)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
@app.command(name="remove-contents")
|
|
330
|
+
def remove_node_contents(
|
|
331
|
+
bigeye_conf: str = global_options.bigeye_conf,
|
|
332
|
+
config_file: str = global_options.config_file,
|
|
333
|
+
workspace: str = global_options.workspace,
|
|
334
|
+
repo_name: str = custom_repo_options.repo_name,
|
|
335
|
+
node: str = custom_repo_options.node,
|
|
336
|
+
asset: Optional[List[str]] = custom_repo_options.asset,
|
|
337
|
+
assets_file: Optional[str] = custom_repo_options.assets_file,
|
|
338
|
+
dry_run: bool = custom_repo_options.dry_run,
|
|
339
|
+
):
|
|
340
|
+
"""Remove assets from a custom node (e.g. unassign them from a CI code). Deletes only the
|
|
341
|
+
containment edge between the node and each asset; the asset and its lineage are untouched."""
|
|
342
|
+
change_node_contents(bigeye_conf, config_file, workspace, repo_name, node, asset, assets_file, dry_run, add=False)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from bigeye_cli.functions import ASSET_LIST_HEADER_HELP
|
|
6
|
+
|
|
7
|
+
repo_name: str = typer.Option(
|
|
8
|
+
...
|
|
9
|
+
, "--repo-name"
|
|
10
|
+
, help="Display name of the custom repository."
|
|
11
|
+
)
|
|
12
|
+
node: str = typer.Option(
|
|
13
|
+
...
|
|
14
|
+
, "--node"
|
|
15
|
+
, help="The repository node to work on (e.g. a CI code): its ID from the workbook's Nodes sheet, "
|
|
16
|
+
"or its display name if that is unique."
|
|
17
|
+
)
|
|
18
|
+
asset: Optional[List[str]] = typer.Option(
|
|
19
|
+
None
|
|
20
|
+
, "--asset"
|
|
21
|
+
, "-a"
|
|
22
|
+
, help="An asset to add or remove: a warehouse FQN (source.[database.]schema[.table[.column]]), a "
|
|
23
|
+
"catalog path (dotted, or ' / '-separated as the catalog shows it), or id:<lineage node id>. "
|
|
24
|
+
"Repeatable."
|
|
25
|
+
)
|
|
26
|
+
assets_file: Optional[str] = typer.Option(
|
|
27
|
+
None
|
|
28
|
+
, "--assets-file"
|
|
29
|
+
, help="CSV or Excel file with one asset per row in the first column. " + ASSET_LIST_HEADER_HELP
|
|
30
|
+
)
|
|
31
|
+
dry_run: bool = typer.Option(
|
|
32
|
+
False
|
|
33
|
+
, "--dry-run"
|
|
34
|
+
, help="Show what would change without writing."
|
|
35
|
+
)
|
|
@@ -14,7 +14,7 @@ from bigeye_sdk.client.datawatch_client import DatawatchClient, datawatch_client
|
|
|
14
14
|
get_all_workspaces_for_login, get_allowed_emails_for_workspace
|
|
15
15
|
from bigeye_sdk.functions.metric_functions import get_file_name_for_metric
|
|
16
16
|
from bigeye_sdk.functions.file_functs import create_subdir_if_not_exists, serialize_listdict_to_json_file, \
|
|
17
|
-
write_to_file, serialize_list_to_json_file
|
|
17
|
+
write_to_file, serialize_list_to_json_file, read_column
|
|
18
18
|
from bigeye_sdk.functions.helpers import str_to_bool
|
|
19
19
|
from bigeye_sdk.log import get_logger
|
|
20
20
|
from bigeye_sdk.authentication.api_authentication import ApiAuth
|
|
@@ -22,6 +22,12 @@ from bigeye_sdk.authentication.config import Config, WorkspaceConfig
|
|
|
22
22
|
from bigeye_sdk.exceptions.exceptions import WorkspaceNotSetException, ConfigNotFoundException
|
|
23
23
|
from bigeye_sdk.generated.com.bigeye.models.generated import MetricInfoList, Table, Issue, MetricTemplate, \
|
|
24
24
|
WorkspaceIdNameAndRole, NamedSchedule, VirtualTable
|
|
25
|
+
from bigeye_sdk.controller.custom_repository_controller import CustomRepositoryController
|
|
26
|
+
from bigeye_sdk.generated.com.bigeye.models.generated import LineageNodeV2
|
|
27
|
+
from bigeye_sdk.model.custom_repository_excel import format_name_path
|
|
28
|
+
from bigeye_sdk.model.custom_repository_models import NodeContentsResult
|
|
29
|
+
from bigeye_sdk.model.enums import ContentsChangeStatus
|
|
30
|
+
from bigeye_sdk.model.lineage_end_to_end import WAREHOUSE_NODE_TYPES, data_node_type
|
|
25
31
|
from bigeye_sdk.model.protobuf_enum_facade import SimpleMetricTemplateParameterType, SimpleFieldType
|
|
26
32
|
from bigeye_sdk.model.protobuf_extensions import MetricDebugQueries
|
|
27
33
|
|
|
@@ -341,3 +347,96 @@ def __str_presenter(dumper, data):
|
|
|
341
347
|
data = "\n".join([line.rstrip() for line in data.splitlines()])
|
|
342
348
|
return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|')
|
|
343
349
|
return dumper.represent_scalar('tag:yaml.org,2002:str', data)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
ASSET_FILE_HEADER_NAMES = frozenset({
|
|
353
|
+
"asset", "assets", "exclude", "exclude asset", "exclude assets", "excluded asset", "excluded assets",
|
|
354
|
+
"fqn", "fqns", "path", "catalog path", "node", "node id",
|
|
355
|
+
})
|
|
356
|
+
ASSET_LIST_HEADER_HELP = (
|
|
357
|
+
"A first row is skipped as a header only if it is one of (in any case): "
|
|
358
|
+
+ ", ".join(sorted(ASSET_FILE_HEADER_NAMES))
|
|
359
|
+
+ ". Any other first row is read as an asset."
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def read_asset_file(path: str) -> List[str]:
|
|
364
|
+
"""The assets listed in the first column of a CSV or Excel file, deduplicated, header row skipped."""
|
|
365
|
+
return list(dict.fromkeys(read_column(path, header_names=ASSET_FILE_HEADER_NAMES)))
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def collect_assets(assets: Optional[List[str]], assets_file: Optional[str]) -> List[str]:
|
|
369
|
+
"""Assets named with --asset plus those listed in --assets-file; ``ValueError`` if the file can't
|
|
370
|
+
be read or no asset is named at all."""
|
|
371
|
+
collected = list(assets or [])
|
|
372
|
+
if assets_file:
|
|
373
|
+
try:
|
|
374
|
+
collected.extend(read_asset_file(assets_file))
|
|
375
|
+
except (OSError, ValueError) as e:
|
|
376
|
+
raise ValueError(f"Could not read --assets-file '{assets_file}': {e}") from e
|
|
377
|
+
if not collected:
|
|
378
|
+
raise ValueError("Name at least one asset with --asset or --assets-file.")
|
|
379
|
+
return collected
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def contained_asset_type(node: LineageNodeV2) -> str:
|
|
383
|
+
"""A contained asset's node type without its DATA_NODE_TYPE_ prefix (UNSPECIFIED when unset)."""
|
|
384
|
+
return data_node_type(node).name.replace("DATA_NODE_TYPE_", "")
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def contained_asset_path(node: LineageNodeV2) -> str:
|
|
388
|
+
"""A contained asset as add/remove-contents accepts it: a dotted FQN for warehouse objects, else
|
|
389
|
+
its ' / ' catalog path, falling back to the node name or ``id:<node id>``."""
|
|
390
|
+
parts = list(node.catalog_path.path_parts) if node.catalog_path else []
|
|
391
|
+
if not parts:
|
|
392
|
+
return node.node_name or f"id:{node.id}"
|
|
393
|
+
return ".".join(parts) if data_node_type(node) in WAREHOUSE_NODE_TYPES else format_name_path(parts)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def node_contents_report(result: NodeContentsResult, dry_run: bool) -> List[str]:
|
|
397
|
+
"""One line per asset of an add/remove-contents run, then a status summary for the node."""
|
|
398
|
+
lines = []
|
|
399
|
+
counts: Dict[str, int] = {}
|
|
400
|
+
for change in result.changes:
|
|
401
|
+
node_ref = f" (id:{change.node_id})" if change.node_id else ""
|
|
402
|
+
detail = f": {change.detail}" if change.detail else ""
|
|
403
|
+
lines.append(f" {change.status.value:<24} {change.asset}{node_ref}{detail}")
|
|
404
|
+
counts[change.status.value] = counts.get(change.status.value, 0) + 1
|
|
405
|
+
summary = ", ".join(f"{n} {status}" for status, n in counts.items())
|
|
406
|
+
lines.append(f"{result.node_name} (node {result.node_id}): {summary}.")
|
|
407
|
+
pending = (ContentsChangeStatus.WOULD_ADD, ContentsChangeStatus.WOULD_REMOVE)
|
|
408
|
+
if dry_run and any(c.status in pending for c in result.changes):
|
|
409
|
+
lines.append("Dry run: nothing was changed. Re-run without --dry-run to apply.")
|
|
410
|
+
return lines
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def change_node_contents(
|
|
414
|
+
bigeye_conf: str,
|
|
415
|
+
config_file: str,
|
|
416
|
+
workspace: str,
|
|
417
|
+
repo_name: str,
|
|
418
|
+
node: str,
|
|
419
|
+
asset: Optional[List[str]],
|
|
420
|
+
assets_file: Optional[str],
|
|
421
|
+
dry_run: bool,
|
|
422
|
+
add: bool,
|
|
423
|
+
) -> None:
|
|
424
|
+
"""Run `catalog custom-repo add-contents` (``add=True``) or `remove-contents`, print the report, and
|
|
425
|
+
exit 1 if any asset failed or the change did not read back."""
|
|
426
|
+
try:
|
|
427
|
+
assets = collect_assets(asset, assets_file)
|
|
428
|
+
controller = CustomRepositoryController(cli_client_factory(bigeye_conf, config_file, workspace))
|
|
429
|
+
update = controller.add_node_contents if add else controller.remove_node_contents
|
|
430
|
+
result = update(repo_name, node, assets, dry_run=dry_run)
|
|
431
|
+
except ValueError as e:
|
|
432
|
+
typer.echo(str(e), err=True)
|
|
433
|
+
raise typer.Exit(code=1)
|
|
434
|
+
typer.echo("\n".join(node_contents_report(result, dry_run)))
|
|
435
|
+
if not result.verified:
|
|
436
|
+
typer.echo(
|
|
437
|
+
f"Some changes did not read back as expected; check with `bigeye catalog custom-repo contents "
|
|
438
|
+
f"--repo-name \"{repo_name}\" --node \"{node}\"`.",
|
|
439
|
+
err=True,
|
|
440
|
+
)
|
|
441
|
+
if result.failed:
|
|
442
|
+
raise typer.Exit(code=1)
|
|
@@ -5,7 +5,7 @@ import typer
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
from bigeye_cli.exceptions.exceptions import InvalidEntityException
|
|
8
|
-
from bigeye_cli.functions import cli_client_factory
|
|
8
|
+
from bigeye_cli.functions import ASSET_LIST_HEADER_HELP, cli_client_factory, read_asset_file
|
|
9
9
|
from bigeye_cli import global_options
|
|
10
10
|
from bigeye_sdk.controller.lineage_controller import LineageController
|
|
11
11
|
from bigeye_sdk.controller.lineage_custom_reporting_controller import LineageCustomReporting
|
|
@@ -14,6 +14,7 @@ from bigeye_sdk.log import get_logger
|
|
|
14
14
|
from bigeye_sdk.model.lineage_facade import LineageConfigurationFile, SimpleLineageConfigurationFile, \
|
|
15
15
|
LineageColumnOverride
|
|
16
16
|
from bigeye_sdk.model.lineage_end_to_end import (
|
|
17
|
+
AssetExclusions,
|
|
17
18
|
DEFAULT_MAX_PATHS,
|
|
18
19
|
EndToEndDirection,
|
|
19
20
|
Granularity,
|
|
@@ -229,7 +230,7 @@ def export_graphs(
|
|
|
229
230
|
fqns_file: str = typer.Option(
|
|
230
231
|
...,
|
|
231
232
|
"--fqns-file",
|
|
232
|
-
help="Path to a CSV with a single column of fully-qualified catalog names.",
|
|
233
|
+
help="Path to a CSV or Excel file with a single column of fully-qualified catalog names.",
|
|
233
234
|
),
|
|
234
235
|
direction: GraphDirection = typer.Option(
|
|
235
236
|
GraphDirection.BOTH,
|
|
@@ -285,7 +286,7 @@ def export_graphs(
|
|
|
285
286
|
|
|
286
287
|
try:
|
|
287
288
|
specs = read_fqns_file(fqns_file)
|
|
288
|
-
except OSError as e:
|
|
289
|
+
except (OSError, ValueError) as e:
|
|
289
290
|
typer.echo(f"Could not read FQNs file '{fqns_file}': {e}", err=True)
|
|
290
291
|
raise typer.Exit(code=1)
|
|
291
292
|
|
|
@@ -375,6 +376,15 @@ def export_end_to_end(
|
|
|
375
376
|
"-o",
|
|
376
377
|
help="Output .csv path. Defaults to end_to_end_<root>.csv in the current directory.",
|
|
377
378
|
),
|
|
379
|
+
exclude_file: Optional[str] = typer.Option(
|
|
380
|
+
None,
|
|
381
|
+
"--exclude-file",
|
|
382
|
+
help="CSV or Excel file listing assets to leave out of the paths, one per row in the first "
|
|
383
|
+
"column. Each is a catalog path or warehouse FQN, as written in the Hop_* columns, or "
|
|
384
|
+
"id:<lineage node id>, and also excludes everything inside it. Paths are joined across "
|
|
385
|
+
"excluded assets, so A <- X <- B with X excluded becomes A <- B. "
|
|
386
|
+
+ ASSET_LIST_HEADER_HELP,
|
|
387
|
+
),
|
|
378
388
|
):
|
|
379
389
|
"""Export complete end-to-end lineage paths for one or more roots into a CSV.
|
|
380
390
|
|
|
@@ -387,6 +397,14 @@ def export_end_to_end(
|
|
|
387
397
|
typer.echo("--depth must be a positive integer (1 or greater).", err=True)
|
|
388
398
|
raise typer.Exit(code=1)
|
|
389
399
|
|
|
400
|
+
exclusions: Optional[AssetExclusions] = None
|
|
401
|
+
if exclude_file:
|
|
402
|
+
try:
|
|
403
|
+
exclusions = AssetExclusions(read_asset_file(exclude_file))
|
|
404
|
+
except (OSError, ValueError) as e:
|
|
405
|
+
typer.echo(f"Could not read --exclude-file '{exclude_file}': {e}", err=True)
|
|
406
|
+
raise typer.Exit(code=1)
|
|
407
|
+
|
|
390
408
|
client = cli_client_factory(bigeye_conf, config_file, workspace)
|
|
391
409
|
controller = LineageCustomReporting(client)
|
|
392
410
|
|
|
@@ -400,6 +418,7 @@ def export_end_to_end(
|
|
|
400
418
|
depth=depth,
|
|
401
419
|
concurrency=concurrency,
|
|
402
420
|
include_etl=include_etl,
|
|
421
|
+
exclusions=exclusions,
|
|
403
422
|
)
|
|
404
423
|
|
|
405
424
|
log.info(
|
|
@@ -408,3 +427,15 @@ def export_end_to_end(
|
|
|
408
427
|
)
|
|
409
428
|
for result in summary.failed:
|
|
410
429
|
log.warning(f" {result.root}: {result.error}")
|
|
430
|
+
removed = {entry: count for entry, count in summary.exclusion_counts.items() if count}
|
|
431
|
+
if removed:
|
|
432
|
+
log.info("Excluded assets (lineage nodes removed from the paths):")
|
|
433
|
+
for entry, count in removed.items():
|
|
434
|
+
log.info(f" {entry}: {count}")
|
|
435
|
+
if summary.unmatched_exclusions:
|
|
436
|
+
log.warning(
|
|
437
|
+
f"{len(summary.unmatched_exclusions)} excluded asset(s) matched nothing in the traced lineage; "
|
|
438
|
+
f"check their spelling against the Hop_* values:"
|
|
439
|
+
)
|
|
440
|
+
for entry in summary.unmatched_exclusions:
|
|
441
|
+
log.warning(f" {entry}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "bigeye-cli"
|
|
3
|
-
version = "0.9.
|
|
3
|
+
version = "0.9.2"
|
|
4
4
|
description = "Bigeye CLI offers developer tools for maintaining your developer workspace."
|
|
5
5
|
license = "Proprietary"
|
|
6
6
|
authors = ["Bigeye <support@bigeye.com>"]
|
|
@@ -19,7 +19,7 @@ bigeye = "bigeye_cli.__main__:app"
|
|
|
19
19
|
[tool.poetry.dependencies]
|
|
20
20
|
python = "^3.11.0"
|
|
21
21
|
click = "^8.1.8"
|
|
22
|
-
bigeye-sdk = "^0.11.
|
|
22
|
+
bigeye-sdk = "^0.11.17"
|
|
23
23
|
rich = "^14.1.0"
|
|
24
24
|
PyGithub = '^1.59.1'
|
|
25
25
|
|
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from datetime import datetime
|
|
3
|
-
from typing import Dict, List, Optional, Tuple
|
|
4
|
-
|
|
5
|
-
import typer
|
|
6
|
-
|
|
7
|
-
from bigeye_cli import global_options
|
|
8
|
-
from bigeye_cli.functions import cli_client_factory
|
|
9
|
-
from bigeye_sdk.controller.custom_repository_controller import CustomRepositoryController
|
|
10
|
-
from bigeye_sdk.log import get_logger
|
|
11
|
-
from bigeye_sdk.model.custom_repository_excel import (
|
|
12
|
-
SHEET_EDGES,
|
|
13
|
-
CustomRepoWorkbook,
|
|
14
|
-
WorkbookValidationError,
|
|
15
|
-
build_config,
|
|
16
|
-
iter_path_endpoints,
|
|
17
|
-
read_workbook,
|
|
18
|
-
validate_workbook,
|
|
19
|
-
write_workbook,
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
log = get_logger(__file__)
|
|
23
|
-
|
|
24
|
-
app = typer.Typer(
|
|
25
|
-
no_args_is_help=True,
|
|
26
|
-
help="Import and export custom repositories as Excel (.xlsx) workbooks.",
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def _print_errors(header: str, errors: List[WorkbookValidationError]) -> None:
|
|
31
|
-
typer.echo(f"{header} with {len(errors)} error(s):", err=True)
|
|
32
|
-
for error in errors:
|
|
33
|
-
typer.echo(f" {error.format()}", err=True)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def _write_unresolved_log(
|
|
37
|
-
log_path: str, repo_name: str, unresolved: List[WorkbookValidationError]
|
|
38
|
-
) -> bool:
|
|
39
|
-
"""Write a log file naming the edge endpoints that could not be resolved to a
|
|
40
|
-
unique Bigeye node — either no match (the column/object does not exist) or more
|
|
41
|
-
than one match (ambiguous). Best-effort: returns False and warns if the file
|
|
42
|
-
cannot be written (the same endpoints are also printed to stderr).
|
|
43
|
-
|
|
44
|
-
Each endpoint is a catalog path 'Repo.schema.table.column' (or a warehouse FQN);
|
|
45
|
-
its final segment is the column/object in question.
|
|
46
|
-
"""
|
|
47
|
-
lines = [
|
|
48
|
-
"Custom repository import — unresolved edge endpoints",
|
|
49
|
-
f"Repository: {repo_name}",
|
|
50
|
-
f"Generated: {datetime.now().isoformat(timespec='seconds')}",
|
|
51
|
-
f"Count: {len(unresolved)}",
|
|
52
|
-
"",
|
|
53
|
-
"These endpoints could not be resolved to a unique Bigeye node (no match, or",
|
|
54
|
-
"more than one), so the edges referencing them were skipped. Each line below",
|
|
55
|
-
"states the reason and the endpoint value.",
|
|
56
|
-
"",
|
|
57
|
-
]
|
|
58
|
-
lines.extend(error.format() for error in unresolved)
|
|
59
|
-
try:
|
|
60
|
-
with open(log_path, "w") as f:
|
|
61
|
-
f.write("\n".join(lines) + "\n")
|
|
62
|
-
return True
|
|
63
|
-
except OSError as e:
|
|
64
|
-
typer.echo(f"Warning: could not write unresolved log to '{log_path}': {e}", err=True)
|
|
65
|
-
return False
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def _resolve_paths(
|
|
69
|
-
wb: CustomRepoWorkbook, controller: CustomRepositoryController
|
|
70
|
-
) -> Tuple[Dict[str, int], List[WorkbookValidationError]]:
|
|
71
|
-
"""Resolve every catalog-path/FQN edge endpoint to a node_id, aggregating failures."""
|
|
72
|
-
resolved: Dict[str, int] = {}
|
|
73
|
-
errors: List[WorkbookValidationError] = []
|
|
74
|
-
endpoints = iter_path_endpoints(wb)
|
|
75
|
-
# Bulk-load every referenced warehouse object up front so the per-endpoint
|
|
76
|
-
# resolution below is a cache lookup rather than several API calls each.
|
|
77
|
-
controller.warm_warehouse_cache(endpoints)
|
|
78
|
-
for endpoint_type, value in endpoints:
|
|
79
|
-
matches = controller.resolve_edge_endpoint(endpoint_type, value)
|
|
80
|
-
if not matches:
|
|
81
|
-
errors.append(
|
|
82
|
-
WorkbookValidationError(
|
|
83
|
-
SHEET_EDGES, "Could not resolve endpoint to a Bigeye node", value=value
|
|
84
|
-
)
|
|
85
|
-
)
|
|
86
|
-
elif len(matches) > 1:
|
|
87
|
-
errors.append(
|
|
88
|
-
WorkbookValidationError(
|
|
89
|
-
SHEET_EDGES,
|
|
90
|
-
f"Endpoint is ambiguous (matched node_ids {matches})",
|
|
91
|
-
value=value,
|
|
92
|
-
)
|
|
93
|
-
)
|
|
94
|
-
else:
|
|
95
|
-
resolved[value] = matches[0]
|
|
96
|
-
return resolved, errors
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
@app.command(name="init")
|
|
100
|
-
def init_repo(
|
|
101
|
-
output: str = typer.Option(
|
|
102
|
-
"custom_repo_template.xlsx",
|
|
103
|
-
"--output",
|
|
104
|
-
"-o",
|
|
105
|
-
help="Output .xlsx path for the blank template.",
|
|
106
|
-
),
|
|
107
|
-
force: bool = typer.Option(
|
|
108
|
-
False,
|
|
109
|
-
"--force",
|
|
110
|
-
help="Overwrite the output file if it already exists.",
|
|
111
|
-
),
|
|
112
|
-
):
|
|
113
|
-
"""Generate a blank custom repository workbook (branded, with the Instructions
|
|
114
|
-
sheet and dropdowns) for you to populate, then import with `custom-repo import`."""
|
|
115
|
-
if os.path.exists(output) and not force:
|
|
116
|
-
typer.echo(
|
|
117
|
-
f"'{output}' already exists. Choose a different --output or pass --force to overwrite.",
|
|
118
|
-
err=True,
|
|
119
|
-
)
|
|
120
|
-
raise typer.Exit(code=1)
|
|
121
|
-
|
|
122
|
-
write_workbook(CustomRepoWorkbook(repo_name=""), output)
|
|
123
|
-
log.info(f"Wrote blank custom repository template to {output}")
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
@app.command(name="import")
|
|
127
|
-
def import_repo(
|
|
128
|
-
bigeye_conf: str = global_options.bigeye_conf,
|
|
129
|
-
config_file: str = global_options.config_file,
|
|
130
|
-
workspace: str = global_options.workspace,
|
|
131
|
-
file: str = typer.Option(
|
|
132
|
-
..., "--file", "-f", help="Path to the custom repository .xlsx workbook."
|
|
133
|
-
),
|
|
134
|
-
cleanup_stale: bool = typer.Option(
|
|
135
|
-
False,
|
|
136
|
-
"--cleanup-stale",
|
|
137
|
-
help="Delete nodes/edges present in Bigeye but missing from the sheet. Off by default.",
|
|
138
|
-
),
|
|
139
|
-
skip_unresolved_edges: bool = typer.Option(
|
|
140
|
-
True,
|
|
141
|
-
"--skip-unresolved-edges/--no-skip-unresolved-edges",
|
|
142
|
-
help="Import the rest instead of aborting when some edge endpoints don't resolve "
|
|
143
|
-
"to a Bigeye node (default: on). Skipped endpoints are written to the unresolved "
|
|
144
|
-
"log. Pass --no-skip-unresolved-edges to abort on any unresolved endpoint.",
|
|
145
|
-
),
|
|
146
|
-
unresolved_log: str = typer.Option(
|
|
147
|
-
"custom_repo_unresolved.log",
|
|
148
|
-
"--unresolved-log",
|
|
149
|
-
help="Path for the unresolved-endpoints log. Defaults to "
|
|
150
|
-
"custom_repo_unresolved.log in the current working directory.",
|
|
151
|
-
),
|
|
152
|
-
):
|
|
153
|
-
"""Import a custom repository from an Excel workbook."""
|
|
154
|
-
client = cli_client_factory(bigeye_conf, config_file, workspace)
|
|
155
|
-
controller = CustomRepositoryController(client)
|
|
156
|
-
|
|
157
|
-
try:
|
|
158
|
-
wb = read_workbook(file)
|
|
159
|
-
except (FileNotFoundError, ValueError) as e:
|
|
160
|
-
typer.echo(f"Could not read workbook '{file}': {e}", err=True)
|
|
161
|
-
raise typer.Exit(code=1)
|
|
162
|
-
|
|
163
|
-
validation_errors = validate_workbook(wb)
|
|
164
|
-
if validation_errors:
|
|
165
|
-
_print_errors("Import validation failed", validation_errors)
|
|
166
|
-
raise typer.Exit(code=1)
|
|
167
|
-
|
|
168
|
-
path_node_ids, unresolved = _resolve_paths(wb, controller)
|
|
169
|
-
if unresolved:
|
|
170
|
-
log_written = _write_unresolved_log(unresolved_log, wb.repo_name, unresolved)
|
|
171
|
-
_print_errors("Could not resolve all catalog path references", unresolved)
|
|
172
|
-
log_hint = f" (see '{unresolved_log}')" if log_written else ""
|
|
173
|
-
if not skip_unresolved_edges:
|
|
174
|
-
typer.echo(
|
|
175
|
-
f"{len(unresolved)} unresolved endpoint(s){log_hint}. "
|
|
176
|
-
f"Re-run with --skip-unresolved-edges to import the rest.",
|
|
177
|
-
err=True,
|
|
178
|
-
)
|
|
179
|
-
raise typer.Exit(code=1)
|
|
180
|
-
typer.echo(
|
|
181
|
-
f"Skipping {len(unresolved)} unresolvable endpoint(s){log_hint} "
|
|
182
|
-
f"and importing the rest.",
|
|
183
|
-
err=True,
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
config = build_config(
|
|
187
|
-
wb,
|
|
188
|
-
path_node_ids=path_node_ids,
|
|
189
|
-
cleanup_stale=cleanup_stale,
|
|
190
|
-
drop_unresolved_edges=skip_unresolved_edges,
|
|
191
|
-
)
|
|
192
|
-
result = controller.sync_from_config(config, wait_for_completion=True)
|
|
193
|
-
|
|
194
|
-
log.info(
|
|
195
|
-
f"Sync complete for '{wb.repo_name}': {result.nodes_synced} nodes, "
|
|
196
|
-
f"{result.edges_synced} edges submitted."
|
|
197
|
-
)
|
|
198
|
-
if result.edges_dropped:
|
|
199
|
-
log.warning(f"{result.edges_dropped} edge(s) dropped during sync:")
|
|
200
|
-
for reason in result.dropped_edge_reasons:
|
|
201
|
-
log.warning(f" - {reason}")
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
@app.command(name="export")
|
|
205
|
-
def export_repo(
|
|
206
|
-
bigeye_conf: str = global_options.bigeye_conf,
|
|
207
|
-
config_file: str = global_options.config_file,
|
|
208
|
-
workspace: str = global_options.workspace,
|
|
209
|
-
repo_name: str = typer.Option(
|
|
210
|
-
..., "--repo-name", help="Display name of the custom repository to export."
|
|
211
|
-
),
|
|
212
|
-
output: Optional[str] = typer.Option(
|
|
213
|
-
None,
|
|
214
|
-
"--output",
|
|
215
|
-
"-o",
|
|
216
|
-
help="Output .xlsx path. Defaults to <repo-name>.xlsx in the current directory.",
|
|
217
|
-
),
|
|
218
|
-
):
|
|
219
|
-
"""Export a custom repository to an Excel workbook."""
|
|
220
|
-
client = cli_client_factory(bigeye_conf, config_file, workspace)
|
|
221
|
-
controller = CustomRepositoryController(client)
|
|
222
|
-
|
|
223
|
-
try:
|
|
224
|
-
wb = controller.export_repository(repo_name)
|
|
225
|
-
except ValueError as e:
|
|
226
|
-
typer.echo(str(e), err=True)
|
|
227
|
-
raise typer.Exit(code=1)
|
|
228
|
-
|
|
229
|
-
output_path = output or f"{repo_name}.xlsx"
|
|
230
|
-
write_workbook(wb, output_path)
|
|
231
|
-
log.info(
|
|
232
|
-
f"Exported repository '{repo_name}' to {output_path} "
|
|
233
|
-
f"({len(wb.nodes)} nodes, {len(wb.edges)} edges)."
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
@app.command(name="delete")
|
|
238
|
-
def delete_repo(
|
|
239
|
-
bigeye_conf: str = global_options.bigeye_conf,
|
|
240
|
-
config_file: str = global_options.config_file,
|
|
241
|
-
workspace: str = global_options.workspace,
|
|
242
|
-
repo_name: str = typer.Option(
|
|
243
|
-
..., "--repo-name", help="Display name of the custom repository to delete."
|
|
244
|
-
),
|
|
245
|
-
yes: bool = typer.Option(
|
|
246
|
-
False,
|
|
247
|
-
"--yes",
|
|
248
|
-
"-y",
|
|
249
|
-
help="Skip the confirmation prompt.",
|
|
250
|
-
),
|
|
251
|
-
):
|
|
252
|
-
"""Entirely delete a custom repository, including its nodes, edges, node types,
|
|
253
|
-
and integration type."""
|
|
254
|
-
client = cli_client_factory(bigeye_conf, config_file, workspace)
|
|
255
|
-
controller = CustomRepositoryController(client)
|
|
256
|
-
|
|
257
|
-
if not yes:
|
|
258
|
-
typer.confirm(
|
|
259
|
-
f"Permanently delete custom repository '{repo_name}' and all its nodes, "
|
|
260
|
-
f"edges, node types, and integration type?",
|
|
261
|
-
abort=True,
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
try:
|
|
265
|
-
result = controller.delete_repository(repo_name)
|
|
266
|
-
except ValueError as e:
|
|
267
|
-
typer.echo(str(e), err=True)
|
|
268
|
-
raise typer.Exit(code=1)
|
|
269
|
-
|
|
270
|
-
summary = f"Deleted custom repository '{result.repository_name}' (id {result.repository_id})"
|
|
271
|
-
if result.integration_type_deleted:
|
|
272
|
-
summary += f", its integration type, and {result.node_types_deleted} node type(s)"
|
|
273
|
-
else:
|
|
274
|
-
summary += " (integration type retained — shared by another repository)"
|
|
275
|
-
log.info(summary + ".")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|