Skip to content

Commit 42f0475

Browse files
Vladimir KontićJoão Silva
authored andcommitted
!40 78 tagets pagination and generators Closes #78 and #49
1 parent fdcba1d commit 42f0475

16 files changed

Lines changed: 186 additions & 161 deletions

examples/call_targets_sdk.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import json
2-
from typing import List
32

43
from probely_cli import Probely, ProbelyException, list_targets
4+
from probely_cli.exceptions import ProbelyRequestFailed
55

66
if __name__ == "__main__":
77

88
Probely.init(api_key="your_api_key")
99

1010
try:
11-
targets_list: List[dict] = list_targets()
12-
13-
for target in targets_list:
11+
targets_generator = list_targets()
12+
for target in targets_generator():
1413
print(json.dumps(target, indent=4))
15-
14+
except ProbelyRequestFailed as request_error:
15+
print("Request to Probely API failed:", request_error)
1616
except ProbelyException as probely_exception:
17-
print("Probely sdk error:", probely_exception)
17+
print("Probely SDK error:", probely_exception)
18+
except Exception as general_error:
19+
print("An unexpected error occurred:", general_error)

probely_cli/cli/commands/targets/delete.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from probely_cli.cli.commands.targets.get import target_filters_handler
1+
from probely_cli.cli.commands.targets.schemas import TargetApiFiltersSchema
2+
from probely_cli.cli.common import prepare_filters_for_api
23
from probely_cli.exceptions import ProbelyCLIValidation
34
from probely_cli.sdk.targets import delete_target, delete_targets, list_targets
45

@@ -7,7 +8,7 @@ def targets_delete_command_handler(args):
78
"""
89
Delete targets
910
"""
10-
filters = target_filters_handler(args)
11+
filters = prepare_filters_for_api(TargetApiFiltersSchema, args)
1112
targets_ids = args.target_ids
1213

1314
if not filters and not targets_ids:
@@ -17,11 +18,15 @@ def targets_delete_command_handler(args):
1718
raise ProbelyCLIValidation("filters and Target IDs are mutually exclusive.")
1819

1920
if filters:
20-
searched_targets = list_targets(targets_filters=filters)
21-
if not searched_targets:
21+
generator = list_targets(targets_filters=filters)
22+
first_target = next(generator, None)
23+
24+
if not first_target:
2225
raise ProbelyCLIValidation("Selected Filters returned no results")
2326

24-
targets_ids = [target.get("id") for target in searched_targets]
27+
targets_ids = [first_target.get("id")] + [
28+
target.get("id") for target in generator
29+
]
2530

2631
if len(targets_ids) == 1:
2732
target_id = delete_target(targets_ids[0])
Lines changed: 16 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,33 @@
1-
import json
2-
import sys
3-
from typing import Dict, List, Union
4-
5-
import marshmallow
6-
import yaml
7-
from marshmallow import Schema
8-
from rich.table import Table
9-
101
from probely_cli.cli.commands.targets.schemas import TargetApiFiltersSchema
11-
from probely_cli.cli.enums import OutputEnum
12-
from probely_cli.cli.renderers import (
13-
get_printable_date,
14-
get_printable_enum_value,
15-
get_printable_labels,
16-
)
2+
from probely_cli.cli.common import prepare_filters_for_api
3+
from probely_cli.cli.enums import EntityTypeEnum, OutputEnum
4+
5+
from probely_cli.cli.renderers import OutputRenderer
176
from probely_cli.exceptions import ProbelyCLIValidation
18-
from probely_cli.sdk.enums import TargetRiskEnum
197
from probely_cli.sdk.targets import list_targets, retrieve_targets
208

21-
TARGET_NEVER_SCANNED_OUTPUT: str = "Never_scanned"
22-
23-
24-
def _get_printable_last_scan_date(target: Dict) -> str:
25-
last_scan_obj: Union[dict, None] = target.get("last_scan", None)
26-
27-
if last_scan_obj is None:
28-
return TARGET_NEVER_SCANNED_OUTPUT
29-
30-
last_scan_start_date_str: Union[str, None] = last_scan_obj.get("started", None)
31-
32-
return get_printable_date(last_scan_start_date_str, TARGET_NEVER_SCANNED_OUTPUT)
33-
34-
35-
def get_tabled_targets(targets_list: List[Dict]) -> Table:
36-
table = Table(box=None)
37-
table.add_column("ID")
38-
table.add_column("NAME")
39-
table.add_column("URL")
40-
table.add_column("RISK")
41-
table.add_column("LAST_SCAN")
42-
table.add_column("LABELS")
43-
44-
for target in targets_list:
45-
asset = target.get("site")
46-
47-
table.add_row(
48-
target.get("id"),
49-
asset.get("name", "N/D"),
50-
asset.get("url"),
51-
get_printable_enum_value(TargetRiskEnum, target["risk"]),
52-
_get_printable_last_scan_date(target), # last_scan
53-
get_printable_labels(target["labels"]),
54-
)
55-
56-
return table
57-
58-
59-
def target_filters_handler(args) -> dict:
60-
filters_schema: Schema = TargetApiFiltersSchema()
61-
try:
62-
api_ready_filters = filters_schema.load(vars(args))
63-
except marshmallow.ValidationError as e:
64-
# TODO: translate validations?
65-
raise ProbelyCLIValidation(str(e))
66-
67-
return api_ready_filters
68-
69-
70-
def build_cmd_output(args, targets_list):
71-
output_type = OutputEnum[args.output] if args.output else None
72-
73-
if output_type == OutputEnum.JSON:
74-
return json.dumps(targets_list, indent=2)
75-
76-
if output_type == OutputEnum.YAML:
77-
return yaml.dump(
78-
targets_list,
79-
indent=2,
80-
width=sys.maxsize, # avoids word wrapping
81-
)
82-
83-
return get_tabled_targets(targets_list)
84-
859

8610
def targets_get_command_handler(args):
8711
"""
8812
Lists all accessible targets of client
8913
"""
90-
filters = target_filters_handler(args)
14+
filters = prepare_filters_for_api(TargetApiFiltersSchema, args)
9115
targets_ids = args.target_ids
9216

9317
if filters and targets_ids:
9418
raise ProbelyCLIValidation("filters and Target IDs are mutually exclusive.")
9519

9620
if targets_ids:
97-
targets_list = retrieve_targets(targets_ids=targets_ids)
21+
targets_generator = retrieve_targets(targets_ids=targets_ids)
9822
else:
99-
targets_list = list_targets(targets_filters=filters)
23+
targets_generator = list_targets(targets_filters=filters)
24+
25+
output_type = OutputEnum[args.output] if args.output else None
10026

101-
cmd_output = build_cmd_output(args, targets_list)
102-
args.console.print(cmd_output)
27+
renderer = OutputRenderer(
28+
records=targets_generator,
29+
output_type=output_type,
30+
console=args.console,
31+
entity_type=EntityTypeEnum.TARGET,
32+
)
33+
renderer.render()

probely_cli/cli/commands/targets/start_scan.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import logging
22

3-
from probely_cli.cli.commands.targets.get import target_filters_handler
3+
from probely_cli.cli.commands.targets.schemas import TargetApiFiltersSchema
44
from probely_cli.cli.common import (
55
display_scans_response_output,
6+
prepare_filters_for_api,
67
validate_and_retrieve_yaml_content,
78
)
89
from probely_cli.exceptions import ProbelyCLIValidation
@@ -25,7 +26,7 @@ def validate_and_retrieve_extra_payload(args):
2526

2627

2728
def start_scans_command_handler(args):
28-
filters = target_filters_handler(args)
29+
filters = prepare_filters_for_api(TargetApiFiltersSchema, args)
2930
targets_ids = args.target_ids
3031

3132
if not filters and not targets_ids:
@@ -37,12 +38,13 @@ def start_scans_command_handler(args):
3738
extra_payload = validate_and_retrieve_extra_payload(args)
3839

3940
if filters:
40-
searched_targets = list_targets(targets_filters=filters)
41+
generator = list_targets(targets_filters=filters)
42+
first_target = next(generator, None)
4143

42-
if not searched_targets:
44+
if not first_target:
4345
raise ProbelyCLIValidation("Selected Filters returned no results")
4446

45-
targets_ids = [target["id"] for target in searched_targets]
47+
targets_ids = [first_target["id"]] + [target["id"] for target in generator]
4648

4749
if len(targets_ids) == 1:
4850
scans = [start_scan(targets_ids[0], extra_payload)]

probely_cli/cli/commands/targets/update.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55

66
import yaml
77

8-
from probely_cli.cli.commands.targets.get import target_filters_handler
9-
from probely_cli.cli.common import validate_and_retrieve_yaml_content
8+
from probely_cli.cli.commands.targets.schemas import TargetApiFiltersSchema
9+
from probely_cli.cli.common import (
10+
prepare_filters_for_api,
11+
validate_and_retrieve_yaml_content,
12+
)
1013
from probely_cli.cli.enums import OutputEnum
1114
from probely_cli.exceptions import ProbelyCLIValidation
1215
from probely_cli.sdk.targets import list_targets, update_target, update_targets
@@ -45,7 +48,7 @@ def update_targets_command_handler(args):
4548
)
4649
payload = validate_and_retrieve_yaml_content(yaml_file_path)
4750

48-
filters = target_filters_handler(args)
51+
filters = prepare_filters_for_api(TargetApiFiltersSchema, args)
4952
targets_ids = args.target_ids
5053

5154
if not filters and not targets_ids:
@@ -65,16 +68,16 @@ def update_targets_command_handler(args):
6568
return
6669

6770
# Fetch all Targets that match the filters and update them
68-
targets_for_update = list_targets(targets_filters=filters)
69-
searched_targets_ids = [target["id"] for target in targets_for_update]
71+
generator = list_targets(targets_filters=filters)
72+
first_target = next(generator, None)
7073

71-
if not searched_targets_ids: # TODO test
74+
if not first_target:
7275
raise ProbelyCLIValidation("Selected Filters returned no results")
7376

74-
target_ids = searched_targets_ids
77+
targets_ids = [first_target["id"]] + [target["id"] for target in generator]
7578

76-
if len(target_ids) == 1:
77-
updated_targets = [update_target(target_ids[0], payload)]
79+
if len(targets_ids) == 1:
80+
updated_targets = [update_target(targets_ids[0], payload)]
7881
else:
79-
updated_targets = update_targets(target_ids, payload)
82+
updated_targets = update_targets(targets_ids, payload)
8083
display_cmd_output(args, updated_targets)

probely_cli/cli/renderers.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import json
22
import sys
33
import textwrap
4-
from typing import Dict, Generator, List, Optional, Type, Union
4+
from typing import (
5+
Dict,
6+
Generator,
7+
List,
8+
Optional,
9+
Type,
10+
Union,
11+
)
512

613
import yaml
714
from dateutil import parser
@@ -12,6 +19,7 @@
1219
from probely_cli.sdk.enums import ProbelyCLIEnum
1320

1421
UNKNOWN_VALUE_REP = "UNKNOWN"
22+
TARGET_NEVER_SCANNED_OUTPUT = "Never_scanned"
1523

1624

1725
class OutputRenderer:
@@ -104,3 +112,14 @@ def get_printable_date(
104112
return default_string
105113

106114
return ""
115+
116+
117+
def get_printable_last_scan_date(target: Dict) -> str:
118+
last_scan_obj: Union[dict, None] = target.get("last_scan", None)
119+
120+
if last_scan_obj is None:
121+
return TARGET_NEVER_SCANNED_OUTPUT
122+
123+
last_scan_start_date_str: Union[str, None] = last_scan_obj.get("started", None)
124+
125+
return get_printable_date(last_scan_start_date_str, TARGET_NEVER_SCANNED_OUTPUT)

probely_cli/cli/tables/finding_table.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from typing import Dict
12
from rich.table import Table
23

34
from probely_cli.cli.renderers import (
@@ -25,7 +26,7 @@ def create_table(self, show_header: bool = False) -> Table:
2526

2627
return table
2728

28-
def add_row(self, table: Table, finding: dict) -> None:
29+
def add_row(self, table: Table, finding: Dict) -> None:
2930
target = finding.get("target", {})
3031

3132
table.add_row(

probely_cli/cli/tables/table_factory.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from probely_cli.cli.tables.base_table import BaseOutputTable
55
from probely_cli.cli.tables.finding_table import FindingTable
66
from probely_cli.cli.tables.scan_table import ScanTable
7+
from probely_cli.cli.tables.targets_table import TargetTable
78

89

910
class TableFactory:
@@ -12,6 +13,7 @@ def get_table_class(entity_type: EntityTypeEnum) -> Type[BaseOutputTable]:
1213
ENTITY_TABLE_MAPPING = {
1314
EntityTypeEnum.FINDING: FindingTable,
1415
EntityTypeEnum.SCAN: ScanTable,
16+
EntityTypeEnum.TARGET: TargetTable,
1517
}
1618

1719
return ENTITY_TABLE_MAPPING[entity_type]
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from typing import Dict
2+
3+
from rich.table import Table
4+
from probely_cli.cli.renderers import (
5+
get_printable_enum_value,
6+
get_printable_labels,
7+
get_printable_last_scan_date,
8+
)
9+
from probely_cli.cli.tables.base_table import BaseOutputTable
10+
from probely_cli.sdk.enums import TargetRiskEnum
11+
12+
13+
class TargetTable(BaseOutputTable):
14+
def create_table(self, show_header: bool = False) -> Table:
15+
table = Table(show_header=show_header, box=None)
16+
17+
table.add_column("ID", width=12)
18+
table.add_column("NAME", width=36, no_wrap=True)
19+
table.add_column("URL", width=48, no_wrap=True)
20+
table.add_column("RISK", width=7)
21+
table.add_column("LAST_SCAN", width=16)
22+
table.add_column("LABELS", width=16, no_wrap=True)
23+
24+
return table
25+
26+
def add_row(self, table: Table, target: Dict) -> None:
27+
site = target.get("site")
28+
29+
table.add_row(
30+
target.get("id"),
31+
site.get("name", "N/D"),
32+
site.get("url"),
33+
get_printable_enum_value(TargetRiskEnum, target["risk"]),
34+
get_printable_last_scan_date(target),
35+
get_printable_labels(target["labels"]),
36+
)

0 commit comments

Comments
 (0)