validio-cli 0.14.0__tar.gz → 0.15.0__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 (27) hide show
  1. {validio_cli-0.14.0 → validio_cli-0.15.0}/PKG-INFO +2 -2
  2. {validio_cli-0.14.0 → validio_cli-0.15.0}/pyproject.toml +2 -2
  3. {validio_cli-0.14.0 → validio_cli-0.15.0}/validio_cli/bin/entities/config.py +4 -1
  4. {validio_cli-0.14.0 → validio_cli-0.15.0}/validio_cli/bin/entities/dbt.py +4 -0
  5. validio_cli-0.15.0/validio_cli/bin/entities/incidents.py +155 -0
  6. {validio_cli-0.14.0 → validio_cli-0.15.0}/validio_cli/bin/entities/notification_rules.py +0 -9
  7. validio_cli-0.14.0/validio_cli/bin/entities/incidents.py +0 -256
  8. {validio_cli-0.14.0 → validio_cli-0.15.0}/LICENSE +0 -0
  9. {validio_cli-0.14.0 → validio_cli-0.15.0}/README_PUBLIC.md +0 -0
  10. {validio_cli-0.14.0 → validio_cli-0.15.0}/validio_cli/__init__.py +0 -0
  11. {validio_cli-0.14.0 → validio_cli-0.15.0}/validio_cli/bin/entities/channels.py +0 -0
  12. {validio_cli-0.14.0 → validio_cli-0.15.0}/validio_cli/bin/entities/code.py +0 -0
  13. {validio_cli-0.14.0 → validio_cli-0.15.0}/validio_cli/bin/entities/credentials.py +0 -0
  14. {validio_cli-0.14.0 → validio_cli-0.15.0}/validio_cli/bin/entities/metrics.py +0 -0
  15. {validio_cli-0.14.0 → validio_cli-0.15.0}/validio_cli/bin/entities/recommendations.py +0 -0
  16. {validio_cli-0.14.0 → validio_cli-0.15.0}/validio_cli/bin/entities/resources.py +0 -0
  17. {validio_cli-0.14.0 → validio_cli-0.15.0}/validio_cli/bin/entities/segmentations.py +0 -0
  18. {validio_cli-0.14.0 → validio_cli-0.15.0}/validio_cli/bin/entities/segments.py +0 -0
  19. {validio_cli-0.14.0 → validio_cli-0.15.0}/validio_cli/bin/entities/sources.py +0 -0
  20. {validio_cli-0.14.0 → validio_cli-0.15.0}/validio_cli/bin/entities/users.py +0 -0
  21. {validio_cli-0.14.0 → validio_cli-0.15.0}/validio_cli/bin/entities/validators.py +0 -0
  22. {validio_cli-0.14.0 → validio_cli-0.15.0}/validio_cli/bin/entities/windows.py +0 -0
  23. {validio_cli-0.14.0 → validio_cli-0.15.0}/validio_cli/bin/main.py +0 -0
  24. {validio_cli-0.14.0 → validio_cli-0.15.0}/validio_cli/components.py +0 -0
  25. {validio_cli-0.14.0 → validio_cli-0.15.0}/validio_cli/metadata.py +0 -0
  26. {validio_cli-0.14.0 → validio_cli-0.15.0}/validio_cli/namespace.py +0 -0
  27. {validio_cli-0.14.0 → validio_cli-0.15.0}/validio_cli/schema.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: validio-cli
3
- Version: 0.14.0
3
+ Version: 0.15.0
4
4
  Summary: CLI tool to interact with the Validio platform
5
5
  Home-page: https://validio.io/
6
6
  License: Apache-2.0
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.10
13
13
  Classifier: Programming Language :: Python :: 3.11
14
14
  Requires-Dist: camel-converter (>=3.0.0,<4.0.0)
15
- Requires-Dist: classdiff (>=0.3.0,<0.4.0)
15
+ Requires-Dist: classdiff (==0.4.0)
16
16
  Requires-Dist: prompt-toolkit (>=3.0.38,<4.0.0)
17
17
  Requires-Dist: pydantic (==2.4.2)
18
18
  Requires-Dist: tabulate (>=0.9.0,<0.10.0)
@@ -3,7 +3,7 @@ name = "validio-cli"
3
3
  # This version does not represent the released version or any tag. For each
4
4
  # release we automatically bump this before building and publishing so this
5
5
  # should be kept at 0.0.1dev1
6
- version = "0.14.0"
6
+ version = "0.15.0"
7
7
  description = "CLI tool to interact with the Validio platform"
8
8
  authors = ["Validio <support@validio.io>"]
9
9
  license = "Apache-2.0"
@@ -15,7 +15,7 @@ readme = "README_PUBLIC.md"
15
15
  [tool.poetry.dependencies]
16
16
  python = "^3.10"
17
17
  camel-converter = "^3.0.0"
18
- classdiff = "^0.3.0"
18
+ classdiff = "0.4.0"
19
19
  validio-sdk = "*"
20
20
  typer = { extras = ["all"], version = "^0.7.0" }
21
21
  prompt-toolkit = "^3.0.38"
@@ -77,6 +77,7 @@ def ns(
77
77
  def get(
78
78
  config_dir: str = ConfigDir,
79
79
  output_format: OutputFormat = OutputFormatOption,
80
+ show_secrets: bool = False,
80
81
  ) -> None:
81
82
  config_path = Path(config_dir) if config_dir is not None else None
82
83
 
@@ -109,7 +110,9 @@ def get(
109
110
  "default_namespace": validio_config.default_namespace,
110
111
  "endpoint": validio_config.endpoint,
111
112
  "access_key": validio_config.access_key,
112
- "access_secret": validio_config.access_secret,
113
+ "access_secret": validio_config._access_secret
114
+ if show_secrets
115
+ else validio_config.access_secret,
113
116
  }
114
117
 
115
118
  if output_format == OutputFormat.JSON:
@@ -19,6 +19,9 @@ async def upload(
19
19
  namespace: str = Namespace(),
20
20
  credential_id: str = typer.Option(..., help="Credential name or ID"),
21
21
  manifest: Path = typer.Option(..., help="Path to the manifest file"),
22
+ job_name: str = typer.Option(
23
+ ..., help="The job that the dbt execution belongs to, e.g. `staging-pipeline`"
24
+ ),
22
25
  run_results: Path = typer.Option(help="Path to the run results file", default=None),
23
26
  ) -> None:
24
27
  vc, cfg = await get_client_and_config(config_dir)
@@ -46,6 +49,7 @@ async def upload(
46
49
  credential_id=resolved_credential_id,
47
50
  manifest=json.dumps(trimmed_manifest),
48
51
  run_results=run_results_content,
52
+ job_name=job_name,
49
53
  )
50
54
  )
51
55
  return print("dbt artifact uploaded successfully")
@@ -0,0 +1,155 @@
1
+ from datetime import datetime, timedelta
2
+ from typing import Any
3
+
4
+ import typer
5
+ from validio_sdk import ValidioError
6
+ from validio_sdk.graphql_client import (
7
+ GetSourceIncidentsSource,
8
+ GetValidatorIncidentsValidator,
9
+ )
10
+ from validio_sdk.graphql_client.input_types import TimeRangeInput
11
+
12
+ from validio_cli import (
13
+ AsyncTyper,
14
+ ConfigDir,
15
+ Namespace,
16
+ OutputFormat,
17
+ OutputFormatOption,
18
+ OutputSettings,
19
+ _format_relative_time,
20
+ get_client_and_config,
21
+ output_json,
22
+ output_text,
23
+ )
24
+ from validio_cli.bin.entities import sources, validators
25
+
26
+ app = AsyncTyper(help="Incidents from validators")
27
+
28
+
29
+ @app.async_command(
30
+ help="""List all incidents.
31
+
32
+ By default you will get incidents from the last hour. You can specify a time
33
+ range for when the incident occurred by specifying when the incident ended.
34
+
35
+ You can list incidents in different ways:
36
+
37
+ * Listing all incidents
38
+
39
+ * Listing all source incidents for a specific source with --source
40
+
41
+ * Listing all incidents for a specific validator with --validator
42
+
43
+ * Listing all incidents for a specific segment with --segment
44
+
45
+ * Listing all incidents for a specific validator and segment with --validator
46
+ and --segment together
47
+ """
48
+ )
49
+ async def get(
50
+ config_dir: str = ConfigDir,
51
+ output_format: OutputFormat = OutputFormatOption,
52
+ namespace: str = Namespace(),
53
+ ended_before: datetime = typer.Option(
54
+ datetime.utcnow(),
55
+ help="The incident ended before this timestamp",
56
+ ),
57
+ ended_after: datetime = typer.Option(
58
+ (datetime.utcnow() - timedelta(hours=1)),
59
+ help="The incident ended after this timestamp",
60
+ ),
61
+ validator: str = typer.Option(None, help="Validator to fetch incidents for"),
62
+ segment: str = typer.Option(None, help="Segment to fetch incidents for"),
63
+ source: str = typer.Option(None, help="Source to fetch incidents for"),
64
+ ) -> None:
65
+ vc, cfg = await get_client_and_config(config_dir)
66
+
67
+ if validator is not None:
68
+ validator_id = await validators.get_validator_id(vc, cfg, validator, namespace)
69
+ if validator_id is None:
70
+ return None
71
+
72
+ if source is not None:
73
+ source_id = await sources.get_source_id(vc, cfg, source, namespace)
74
+ if source_id is None:
75
+ return None
76
+
77
+ # TODO(UI-2006): These should all support namespace
78
+
79
+ incidents: (None | GetValidatorIncidentsValidator | GetSourceIncidentsSource) = None
80
+
81
+ if validator:
82
+ incidents = await vc.get_validator_incidents(
83
+ id=validator_id,
84
+ range=TimeRangeInput(
85
+ start=ended_after,
86
+ end=ended_before,
87
+ ),
88
+ segment_id=segment,
89
+ )
90
+ elif source:
91
+ incidents = await vc.get_source_incidents(
92
+ id=source_id,
93
+ range=TimeRangeInput(
94
+ start=ended_after,
95
+ end=ended_before,
96
+ ),
97
+ )
98
+ else:
99
+ raise ValidioError(
100
+ "You need to specify one of --validator and/or --segment or --source"
101
+ )
102
+
103
+ if not incidents:
104
+ return output_text(None, {})
105
+
106
+ if output_format == OutputFormat.JSON:
107
+ return output_json(incidents)
108
+
109
+ return output_text(
110
+ incidents.incidents,
111
+ fields={
112
+ "operator": OutputSettings(
113
+ attribute_name="metric",
114
+ reformat=calculate_operator,
115
+ ),
116
+ "bound": OutputSettings(
117
+ attribute_name="metric",
118
+ reformat=calculate_bound,
119
+ ),
120
+ "value": OutputSettings(
121
+ attribute_name="metric",
122
+ reformat=lambda x: x.value,
123
+ ),
124
+ "age": OutputSettings(
125
+ attribute_name="metric",
126
+ reformat=lambda x: _format_relative_time(x.end_time),
127
+ ),
128
+ },
129
+ )
130
+
131
+
132
+ def calculate_operator(item: Any) -> str:
133
+ type_ = item.typename__[len("ValidatorMetricWith") :]
134
+ if type_ == "DynamicThreshold":
135
+ operator = item.decision_bounds_type
136
+ else:
137
+ operator = item.operator
138
+
139
+ return f"{type_}/{operator}"
140
+
141
+
142
+ def calculate_bound(item: Any) -> str:
143
+ type_ = item.typename__[len("ValidatorMetricWith") :]
144
+ if type_ == "DynamicThreshold":
145
+ bound = f"{item.lower_bound:.2f} - {item.upper_bound:.2f}"
146
+ elif type_ == "FixedThreshold":
147
+ bound = item.bound
148
+ else:
149
+ bound = "-"
150
+
151
+ return bound
152
+
153
+
154
+ if __name__ == "__main__":
155
+ typer.run(app())
@@ -28,9 +28,6 @@ async def get(
28
28
  output_format: OutputFormat = OutputFormatOption,
29
29
  namespace: str = Namespace(),
30
30
  identifier: str = Identifier,
31
- source: str = typer.Option(
32
- None, help="List notification rules for this source (ID)"
33
- ),
34
31
  ) -> None:
35
32
  vc, cfg = await get_client_and_config(config_dir)
36
33
 
@@ -46,12 +43,6 @@ async def get(
46
43
  filter=ResourceFilter(resource_namespace=get_namespace(namespace, cfg))
47
44
  )
48
45
 
49
- notification_rules = [
50
- nr
51
- for nr in notification_rules
52
- if source is None or (nr is not None and source in nr.sources)
53
- ]
54
-
55
46
  # TODO(UI-2311): Fully support list/get/get_by_resource_name
56
47
  notification_rules = _single_resource_if_specified(notification_rules, identifier)
57
48
 
@@ -1,256 +0,0 @@
1
- from dataclasses import dataclass
2
- from datetime import datetime, timedelta
3
- from typing import Any
4
-
5
- import typer
6
- from validio_sdk import ValidioError
7
- from validio_sdk.graphql_client import (
8
- GetSegmentIncidentsSegmentIncidents,
9
- GetSourceIncidentsSourceIncidentsSegmentLimitExceededNotification,
10
- GetValidatorIncidentsValidatorIncidents,
11
- GetValidatorSegmentIncidentsValidatorSegmentIncidents,
12
- )
13
- from validio_sdk.graphql_client.input_types import (
14
- SegmentIncidentsInput,
15
- SourceIncidentsInput,
16
- TimeRangeInput,
17
- ValidatorIncidentsInput,
18
- ValidatorSegmentIncidentsInput,
19
- )
20
-
21
- from validio_cli import (
22
- AsyncTyper,
23
- ConfigDir,
24
- Namespace,
25
- OutputFormat,
26
- OutputFormatOption,
27
- OutputSettings,
28
- _format_relative_time,
29
- get_client_and_config,
30
- output_json,
31
- output_text,
32
- )
33
- from validio_cli.bin.entities import sources, validators
34
-
35
- app = AsyncTyper(help="Incidents from validators")
36
-
37
-
38
- @app.async_command(
39
- help="""List all incidents.
40
-
41
- By default you will get incidents from the last hour. You can specify a time
42
- range for when the incident occurred by specifying when the incident ended.
43
-
44
- You can list incidents in different ways:
45
-
46
- * Listing all incidents
47
-
48
- * Listing all source incidents for a specific source with --source
49
-
50
- * Listing all incidents for a specific validator with --validator
51
-
52
- * Listing all incidents for a specific segment with --segment
53
-
54
- * Listing all incidents for a specific validator and segment with --validator
55
- and --segment together
56
- """
57
- )
58
- async def get(
59
- config_dir: str = ConfigDir,
60
- output_format: OutputFormat = OutputFormatOption,
61
- namespace: str = Namespace(),
62
- ended_before: datetime = typer.Option(
63
- datetime.utcnow(),
64
- help="The incident ended before this timestamp",
65
- ),
66
- ended_after: datetime = typer.Option(
67
- (datetime.utcnow() - timedelta(hours=1)),
68
- help="The incident ended after this timestamp",
69
- ),
70
- validator: str = typer.Option(None, help="Validator to fetch incidents for"),
71
- segment: str = typer.Option(None, help="Segment to fetch incidents for"),
72
- source: str = typer.Option(None, help="Source to fetch incidents for"),
73
- ) -> None:
74
- vc, cfg = await get_client_and_config(config_dir)
75
-
76
- if validator is not None:
77
- validator_id = await validators.get_validator_id(vc, cfg, validator, namespace)
78
- if validator_id is None:
79
- return None
80
-
81
- if source is not None:
82
- source_id = await sources.get_source_id(vc, cfg, source, namespace)
83
- if source_id is None:
84
- return None
85
-
86
- # TODO(UI-2006): These should all support namespace
87
-
88
- incidents: (
89
- None
90
- | list[GetValidatorIncidentsValidatorIncidents]
91
- | list[GetValidatorSegmentIncidentsValidatorSegmentIncidents]
92
- | list[GetSegmentIncidentsSegmentIncidents]
93
- ) = None
94
-
95
- if validator and segment:
96
- incidents = await vc.get_validator_segment_incidents(
97
- ValidatorSegmentIncidentsInput(
98
- time_range=TimeRangeInput(
99
- start=ended_after,
100
- end=ended_before,
101
- ),
102
- validator_id=validator_id,
103
- segment_id=segment,
104
- )
105
- )
106
- elif validator:
107
- incidents = await vc.get_validator_incidents(
108
- ValidatorIncidentsInput(
109
- time_range=TimeRangeInput(
110
- start=ended_after,
111
- end=ended_before,
112
- ),
113
- validator_id=validator_id,
114
- )
115
- )
116
- elif segment:
117
- incidents = await vc.get_segment_incidents(
118
- SegmentIncidentsInput(
119
- time_range=TimeRangeInput(
120
- start=ended_after,
121
- end=ended_before,
122
- ),
123
- segment_id=segment,
124
- )
125
- )
126
- elif source:
127
- # Since source incidents looks completely different we hand them off to
128
- # a separate function, we can't re-use any of the rendering.
129
- return _output_text_source_incidents(
130
- output_format,
131
- await vc.get_source_incidents(
132
- SourceIncidentsInput(
133
- time_range=TimeRangeInput(
134
- start=ended_after,
135
- end=ended_before,
136
- ),
137
- source_id=source_id,
138
- )
139
- ),
140
- )
141
- else:
142
- raise ValidioError(
143
- "You need to specify one of --validator and/or --segment or --source"
144
- )
145
-
146
- if output_format == OutputFormat.JSON:
147
- return output_json(incidents)
148
-
149
- return output_text(
150
- incidents,
151
- fields={
152
- "operator": OutputSettings(
153
- attribute_name="metric",
154
- reformat=calculate_operator,
155
- ),
156
- "bound": OutputSettings(
157
- attribute_name="metric",
158
- reformat=calculate_bound,
159
- ),
160
- "value": OutputSettings(
161
- attribute_name="metric",
162
- reformat=lambda x: x.value,
163
- ),
164
- "age": OutputSettings(
165
- attribute_name="metric",
166
- reformat=lambda x: _format_relative_time(x.end_time),
167
- ),
168
- },
169
- )
170
-
171
-
172
- def _output_text_source_incidents(
173
- output_format: OutputFormat, incidents: list[Any]
174
- ) -> None:
175
- if output_format == OutputFormat.JSON:
176
- return output_json(incidents)
177
-
178
- @dataclass
179
- class SchemaChange:
180
- field: str
181
- change_type: str
182
- old_value: str
183
- new_value: str
184
-
185
- change_type_to_key = {
186
- "Nullability": "nullable",
187
- "JtdType": "type",
188
- "UnderlyingType": "underlying_type",
189
- }
190
-
191
- changes = []
192
- for item in incidents:
193
- # TODO: We only show schema changes for now
194
- if item.typename__ != "SchemaChangeNotification":
195
- continue
196
-
197
- for field, field_changes in item.payload.items():
198
- for ct in field_changes["change_types"]:
199
- k = change_type_to_key.get(ct)
200
-
201
- changes.append(
202
- SchemaChange(
203
- field=field,
204
- change_type=ct,
205
- old_value=field_changes["old"][k] if k else "-",
206
- new_value=field_changes["new"][k] if k else "-",
207
- )
208
- )
209
-
210
- return output_text(
211
- changes,
212
- fields={
213
- "field": None,
214
- "change_type": None,
215
- "old_value": None,
216
- "new_value": None,
217
- },
218
- )
219
-
220
-
221
- def calculate_operator(item: Any) -> str:
222
- type_ = item.typename__[len("ValidatorMetricWith") :]
223
- if type_ == "DynamicThreshold":
224
- operator = item.decision_bounds_type
225
- else:
226
- operator = item.operator
227
-
228
- return f"{type_}/{operator}"
229
-
230
-
231
- def calculate_bound(item: Any) -> str:
232
- type_ = item.typename__[len("ValidatorMetricWith") :]
233
- if type_ == "DynamicThreshold":
234
- bound = f"{item.lower_bound:.2f} - {item.upper_bound:.2f}"
235
- elif type_ == "FixedThreshold":
236
- bound = item.bound
237
- else:
238
- bound = "-"
239
-
240
- return bound
241
-
242
-
243
- def _output_text_source_incidents_segment_limit_exceeded(
244
- incidents: list[GetSourceIncidentsSourceIncidentsSegmentLimitExceededNotification],
245
- ) -> None:
246
- return output_text(
247
- incidents,
248
- fields={
249
- "limit": None,
250
- "age": OutputSettings(attribute_name="created_at"),
251
- },
252
- )
253
-
254
-
255
- if __name__ == "__main__":
256
- typer.run(app())
File without changes