pyntcli 0.1.29__py3-none-any.whl → 0.1.31__py3-none-any.whl

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 (66) hide show
  1. build/lib/build/lib/build/lib/pyntcli/__init__.py +1 -0
  2. build/lib/build/lib/build/lib/pyntcli/analytics/__init__.py +0 -0
  3. build/lib/build/lib/build/lib/pyntcli/analytics/send.py +70 -0
  4. build/lib/build/lib/build/lib/pyntcli/auth/__init__.py +0 -0
  5. build/lib/build/lib/build/lib/pyntcli/auth/login.py +156 -0
  6. build/lib/build/lib/build/lib/pyntcli/commands/__init__.py +0 -0
  7. build/lib/build/lib/build/lib/pyntcli/commands/har.py +68 -0
  8. build/lib/build/lib/build/lib/pyntcli/commands/id_command.py +28 -0
  9. build/lib/build/lib/build/lib/pyntcli/commands/newman.py +85 -0
  10. build/lib/build/lib/build/lib/pyntcli/commands/postman.py +58 -0
  11. build/lib/build/lib/build/lib/pyntcli/commands/proxy.py +145 -0
  12. build/lib/build/lib/build/lib/pyntcli/commands/pynt_cmd.py +47 -0
  13. build/lib/build/lib/build/lib/pyntcli/commands/root.py +57 -0
  14. build/lib/build/lib/build/lib/pyntcli/commands/sub_command.py +15 -0
  15. build/lib/build/lib/build/lib/pyntcli/commands/util.py +26 -0
  16. build/lib/build/lib/build/lib/pyntcli/main.py +77 -0
  17. build/lib/build/lib/build/lib/pyntcli/pynt_docker/__init__.py +1 -0
  18. build/lib/build/lib/build/lib/pyntcli/pynt_docker/pynt_container.py +178 -0
  19. build/lib/build/lib/build/lib/pyntcli/store/__init__.py +1 -0
  20. build/lib/build/lib/build/lib/pyntcli/store/json_connector.py +23 -0
  21. build/lib/build/lib/build/lib/pyntcli/store/store.py +55 -0
  22. build/lib/build/lib/build/lib/pyntcli/store/store_connector.py +17 -0
  23. build/lib/build/lib/build/lib/pyntcli/ui/__init__.py +0 -0
  24. build/lib/build/lib/build/lib/pyntcli/ui/progress.py +31 -0
  25. build/lib/build/lib/build/lib/pyntcli/ui/ui_thread.py +168 -0
  26. build/lib/build/lib/build/lib/tests/auth/test_login.py +96 -0
  27. build/lib/build/lib/build/lib/tests/conftest.py +24 -0
  28. build/lib/build/lib/build/lib/tests/store/test_cred_store.py +11 -0
  29. build/lib/build/lib/pyntcli/__init__.py +1 -0
  30. build/lib/build/lib/pyntcli/analytics/__init__.py +0 -0
  31. build/lib/build/lib/pyntcli/analytics/send.py +70 -0
  32. build/lib/build/lib/pyntcli/auth/__init__.py +0 -0
  33. build/lib/build/lib/pyntcli/auth/login.py +156 -0
  34. build/lib/build/lib/pyntcli/commands/__init__.py +0 -0
  35. build/lib/build/lib/pyntcli/commands/har.py +68 -0
  36. build/lib/build/lib/pyntcli/commands/id_command.py +28 -0
  37. build/lib/build/lib/pyntcli/commands/newman.py +85 -0
  38. build/lib/build/lib/pyntcli/commands/postman.py +58 -0
  39. build/lib/build/lib/pyntcli/commands/proxy.py +147 -0
  40. build/lib/build/lib/pyntcli/commands/pynt_cmd.py +47 -0
  41. build/lib/build/lib/pyntcli/commands/root.py +57 -0
  42. build/lib/build/lib/pyntcli/commands/sub_command.py +15 -0
  43. build/lib/build/lib/pyntcli/commands/util.py +26 -0
  44. build/lib/build/lib/pyntcli/main.py +77 -0
  45. build/lib/build/lib/pyntcli/pynt_docker/__init__.py +1 -0
  46. build/lib/build/lib/pyntcli/pynt_docker/pynt_container.py +178 -0
  47. build/lib/build/lib/pyntcli/store/__init__.py +1 -0
  48. build/lib/build/lib/pyntcli/store/json_connector.py +23 -0
  49. build/lib/build/lib/pyntcli/store/store.py +55 -0
  50. build/lib/build/lib/pyntcli/store/store_connector.py +17 -0
  51. build/lib/build/lib/pyntcli/ui/__init__.py +0 -0
  52. build/lib/build/lib/pyntcli/ui/progress.py +31 -0
  53. build/lib/build/lib/pyntcli/ui/ui_thread.py +168 -0
  54. build/lib/build/lib/tests/auth/test_login.py +96 -0
  55. build/lib/build/lib/tests/conftest.py +24 -0
  56. build/lib/build/lib/tests/store/test_cred_store.py +11 -0
  57. build/lib/pyntcli/__init__.py +1 -1
  58. build/lib/pyntcli/commands/proxy.py +1 -0
  59. pyntcli/__init__.py +1 -1
  60. pyntcli/commands/proxy.py +0 -2
  61. {pyntcli-0.1.29.dist-info → pyntcli-0.1.31.dist-info}/METADATA +1 -1
  62. pyntcli-0.1.31.dist-info/RECORD +117 -0
  63. pyntcli-0.1.29.dist-info/RECORD +0 -61
  64. {pyntcli-0.1.29.dist-info → pyntcli-0.1.31.dist-info}/WHEEL +0 -0
  65. {pyntcli-0.1.29.dist-info → pyntcli-0.1.31.dist-info}/entry_points.txt +0 -0
  66. {pyntcli-0.1.29.dist-info → pyntcli-0.1.31.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,57 @@
1
+ import argparse
2
+ import sys
3
+
4
+ from pyntcli.auth import login
5
+ from pyntcli.ui import ui_thread
6
+ from pyntcli.analytics import send as analytics
7
+
8
+ def root_usage():
9
+ return ui_thread.PrinterText("Usage:",style=ui_thread.PrinterText.HEADER) \
10
+ .with_line("\tpynt [COMMAND] [OPTIONS]") \
11
+ .with_line("") \
12
+ .with_line("Commands:",style=ui_thread.PrinterText.HEADER) \
13
+ .with_line("\tpostman - integration with postman, run scan from pynt postman collection") \
14
+ .with_line("\tnewman - run postman collection from the CLI") \
15
+ .with_line("\tpynt-id - view your pynt-id to use when running pynt in CI pipeline") \
16
+ .with_line("") \
17
+ .with_line("Run pynt [COMMAND] -h to get help on a specific command",style=ui_thread.PrinterText.INFO)
18
+
19
+ def usage(*args):
20
+ ui_thread.print(root_usage())
21
+
22
+ class BaseCommand():
23
+ def __init__(self) -> None:
24
+ self.base: argparse.ArgumentParser = None
25
+ self.subparser: argparse._SubParsersAction = None
26
+
27
+ def cmd(self):
28
+ if self.base:
29
+ return self.base
30
+
31
+ self.base = argparse.ArgumentParser(prog="pynt")
32
+ self.base.print_usage = usage
33
+ self.base.print_help = usage
34
+ return self.base
35
+
36
+ def add_base_arguments(self, parser):
37
+ parser.add_argument("--insecure", default=False, required=False, action='store_true',help="use when target uses self signed certificates")
38
+ parser.add_argument("--dev-flags", type=str,default="", help=argparse.SUPPRESS)
39
+
40
+ def get_subparser(self) -> argparse._SubParsersAction:
41
+ if self.subparser is None:
42
+ self.subparser = self.base.add_subparsers(help="", dest="command")
43
+
44
+ return self.subparser
45
+
46
+ def run_cmd(self, args: argparse.Namespace):
47
+ analytics.emit(analytics.LOGIN_START)
48
+
49
+ if login.should_login():
50
+ l = login.Login().login()
51
+ else:
52
+ login.refresh_token()
53
+
54
+ analytics.emit(analytics.LOGIN_DONE)
55
+ user_id = login.user_id()
56
+ if user_id:
57
+ analytics.set_user_id(user_id)
@@ -0,0 +1,15 @@
1
+ import argparse
2
+
3
+ class PyntSubCommand:
4
+ def __init__(self, name) -> None:
5
+ self.name = name
6
+ pass
7
+
8
+ def get_name(self):
9
+ return self.name
10
+
11
+ def add_cmd(self, parent: argparse._SubParsersAction) -> argparse.ArgumentParser:
12
+ raise NotImplemented()
13
+
14
+ def run_cmd(self, args: argparse.Namespace):
15
+ raise NotImplemented()
@@ -0,0 +1,26 @@
1
+ import requests
2
+ import time
3
+ import socket
4
+
5
+ def is_port_in_use(port: int) -> bool:
6
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
7
+ return s.connect_ex(('localhost', port)) == 0
8
+
9
+ def find_open_port() -> int:
10
+ with socket.socket() as s:
11
+ s.bind(('', 0))
12
+ return s.getsockname()[1]
13
+
14
+ HEALTHCHECK_TIMEOUT = 10
15
+ HEALTHCHECK_INTERVAL = 2
16
+
17
+ def wait_for_healthcheck(address):
18
+ start = time.time()
19
+ while start + HEALTHCHECK_TIMEOUT > time.time():
20
+ try:
21
+ res = requests.get(address + "/healthcheck")
22
+ if res.status_code == 418:
23
+ return
24
+ except:
25
+ time.sleep(HEALTHCHECK_INTERVAL)
26
+ raise TimeoutError()
@@ -0,0 +1,77 @@
1
+ from sys import argv, exit
2
+ import signal
3
+
4
+ from pyntcli import __version__ as cli_version
5
+ from pyntcli.commands import pynt_cmd
6
+ from pyntcli.pynt_docker import pynt_container
7
+ from pyntcli.ui import ui_thread
8
+ from pyntcli.auth import login
9
+ from pyntcli.analytics import send as analytics
10
+
11
+ def signal_handler(signal_number, frame):
12
+ ui_thread.print(ui_thread.PrinterText("Exiting..."))
13
+
14
+ analytics.stop()
15
+ pynt_container.PyntContainerRegistery.instance().stop_all_containers()
16
+ ui_thread.stop()
17
+
18
+ exit(0)
19
+
20
+ import requests
21
+ from distutils.version import StrictVersion
22
+ VERSION_CHECK_URL = "https://pypi.org/pypi/pyntcli/json"
23
+
24
+ def check_is_latest_version(current_version):
25
+ res = requests.get(VERSION_CHECK_URL)
26
+ res.raise_for_status()
27
+
28
+ avail_versions = list(res.json()["releases"].keys())
29
+ avail_versions.sort(key=StrictVersion)
30
+
31
+ if current_version != avail_versions[-1]:
32
+ ui_thread.print(ui_thread.PrinterText("""Pynt CLI new version is available, upgrade now with:
33
+ python3 -m pip install --upgrade pyntcli""",ui_thread.PrinterText.WARNING))
34
+
35
+ def check_for_dependecies():
36
+ pynt_container.get_docker_type()
37
+
38
+ def print_header():
39
+ ui_thread.print(ui_thread.PrinterText(*ui_thread.pynt_header()) \
40
+ .with_line(*ui_thread.pynt_version()) \
41
+ .with_line(""))
42
+ def start_analytics():
43
+ user_id = login.user_id()
44
+ if user_id:
45
+ analytics.set_user_id(user_id)
46
+
47
+ analytics.emit(analytics.CLI_START)
48
+
49
+ def main():
50
+ print_header()
51
+ try:
52
+ start_analytics()
53
+ check_for_dependecies()
54
+ check_is_latest_version(cli_version)
55
+ signal.signal(signal.SIGINT, signal_handler)
56
+ cli = pynt_cmd.PyntCommand()
57
+ cli.run_cmd(cli.parse_args(argv[1:]))
58
+ analytics.stop()
59
+ except pynt_cmd.PyntCommandException:
60
+ pynt_cmd.root.usage()
61
+ except pynt_container.DockerNotAvailableException:
62
+ ui_thread.print(ui_thread.PrinterText("Docker was unavailable, please make sure docker is installed and running.", ui_thread.PrinterText.WARNING))
63
+ analytics.emit(analytics.ERROR, {"error": "docker unavailable"})
64
+ except requests.exceptions.SSLError:
65
+ ui_thread.print(ui_thread.PrinterText("We encountered SSL issues and could not proceed, this may be the cause of a VPN or a Firewall in place.", ui_thread.PrinterText.WARNING))
66
+ analytics.emit(analytics.ERROR, {"error": "ssl issue"})
67
+ except login.Timeout:
68
+ ui_thread.print(ui_thread.PrinterText("Pynt CLI exited due to incomplete registration, please try again.", ui_thread.PrinterText.WARNING))
69
+ analytics.emit(analytics.ERROR, {"error": "login timeout"})
70
+ except login.InvalidTokenInEnvVarsException:
71
+ ui_thread.print(ui_thread.PrinterText("Pynt CLI exited due to malformed credentials provided in env vars.", ui_thread.PrinterText.WARNING))
72
+ analytics.emit(analytics.ERROR, {"error": "invalid pynt cli credentials in env vars"})
73
+ finally:
74
+ ui_thread.stop()
75
+
76
+ if __name__ == "__main__":
77
+ main()
@@ -0,0 +1 @@
1
+ from .pynt_container import *
@@ -0,0 +1,178 @@
1
+ import docker
2
+ from docker.errors import DockerException
3
+ from docker.types import Mount
4
+ import os
5
+ from typing import List
6
+
7
+ from pyntcli.ui import ui_thread
8
+
9
+ PYNT_DOCKER_IMAGE = "ghcr.io/pynt-io/pynt"
10
+
11
+ def create_mount(src, destination, mount_type="bind"):
12
+ return Mount(target=destination, source=src, type=mount_type)
13
+
14
+ class DockerNotAvailableException(Exception):
15
+ pass
16
+
17
+ def get_docker_type():
18
+ try:
19
+ c = docker.from_env()
20
+ version_data = c.version()
21
+ platform = version_data.get("Platform")
22
+
23
+ if platform and platform.get("Name"):
24
+ return platform.get("Name")
25
+
26
+ return ""
27
+
28
+ except DockerException:
29
+ raise DockerNotAvailableException()
30
+ except Exception: #TODO: This is since windows is not behaving nice
31
+ raise DockerNotAvailableException()
32
+
33
+ class PyntDockerPort:
34
+ def __init__(self, src, dest, name) -> None:
35
+ self.src = src
36
+ self.dest = dest
37
+ self.name = name
38
+
39
+ def get_container_with_arguments(*args: PyntDockerPort):
40
+ if "desktop" in get_docker_type().lower():
41
+ ports = {}
42
+ for p in args:
43
+ ports[str(p.src)] = int(p.dest)
44
+ docker_type = PyntDockerDesktopContainer(ports=ports)
45
+ return docker_type , []
46
+
47
+ docker_arguments = []
48
+ docker_type = PyntNativeContainer(network="host")
49
+
50
+ for p in args:
51
+ docker_arguments.append(p.name)
52
+ docker_arguments.append(str(p.dest))
53
+
54
+ return docker_type, docker_arguments
55
+
56
+ def _container_image_from_tag(tag: str) -> str:
57
+ if ":" in tag:
58
+ return tag.split(":")[0]
59
+
60
+ return tag
61
+
62
+ class PyntContainer():
63
+ def __init__(self, image_name, tag, mounts, detach, args) -> None:
64
+ self.docker_client: docker.DockerClient = None
65
+ self.image = image_name if not os.environ.get("IMAGE") else os.environ.get("IMAGE")
66
+ self.tag = tag if not os.environ.get("TAG") else os.environ.get("TAG")
67
+ self.mounts = mounts
68
+ self.detach = detach
69
+ self.stdout = None
70
+ self.running = False
71
+ self.args = args
72
+ self.container_name = ""
73
+
74
+ def _create_docker_client(self):
75
+ self.docker_client = docker.from_env()
76
+ pat = os.environ.get("DOCKER_PASSWORD")
77
+ username = os.environ.get("DOCKER_USERNAME")
78
+ registry = os.environ.get("DOCKER_REGISTRY")
79
+ if pat and username and registry:
80
+ self.docker_client.login(username=username, password=pat, registry=registry)
81
+
82
+ def _is_docker_image_up_to_date(self, image):
83
+ return True
84
+
85
+ def _handle_outdated_docker_image(self, image):
86
+ return image
87
+
88
+ def kill_other_instances(self):
89
+ for c in self.docker_client.containers.list():
90
+ if _container_image_from_tag(c.image.tags[0]) == self.image:
91
+ c.kill()
92
+
93
+ def stop(self):
94
+ if not self.running:
95
+ return
96
+
97
+ self.kill_other_instances()
98
+
99
+ self.docker_client.close()
100
+ self.docker_client = None
101
+ self.running = False
102
+
103
+ def is_alive(self):
104
+ if not self.docker_client or not self.container_name:
105
+ return False
106
+
107
+ l = self.docker_client.containers.list(filters={"name": self.container_name})
108
+ if len(l) != 1:
109
+ return False
110
+
111
+ return l[0].status == "running"
112
+
113
+ def run(self, integration_docker):
114
+ if not self.docker_client:
115
+ self._create_docker_client()
116
+
117
+ self.running = True
118
+ self.kill_other_instances()
119
+
120
+ ui_thread.print(ui_thread.PrinterText("Pulling latest docker", ui_thread.PrinterText.INFO))
121
+ image = self.docker_client.images.pull(self.image, tag=self.tag)
122
+ if not self._is_docker_image_up_to_date(image):
123
+ image = self._handle_outdated_docker_image(image)
124
+ ui_thread.print(ui_thread.PrinterText("Docker pull done", ui_thread.PrinterText.INFO))
125
+
126
+ args = self.args if self.args else None
127
+
128
+ run_arguments = {
129
+ "image":image,
130
+ "detach":self.detach,
131
+ "mounts":self.mounts,
132
+ "stream": True,
133
+ "remove": True,
134
+ "command": args
135
+ }
136
+
137
+ run_arguments.update(integration_docker.get_argumets())
138
+
139
+ c = self.docker_client.containers.run(**run_arguments)
140
+ self.container_name = c.name
141
+ self.stdout = c.logs(stream=True)
142
+
143
+ PyntContainerRegistery.instance().register_container(self)
144
+
145
+ class PyntDockerDesktopContainer():
146
+ def __init__(self, ports) -> None:
147
+ self.ports = ports
148
+
149
+ def get_argumets(self):
150
+ return {"ports": self.ports} if self.ports else {}
151
+
152
+ class PyntNativeContainer():
153
+ def __init__(self, network) -> None:
154
+ self.network = network
155
+
156
+ def get_argumets(self):
157
+ return {"network": self.network} if self.network else {}
158
+
159
+
160
+ class PyntContainerRegistery():
161
+ _instance = None
162
+
163
+ def __init__(self) -> None:
164
+ self.containers: List[PyntContainer] = []
165
+
166
+ @staticmethod
167
+ def instance():
168
+ if not PyntContainerRegistery._instance:
169
+ PyntContainerRegistery._instance = PyntContainerRegistery()
170
+
171
+ return PyntContainerRegistery._instance
172
+
173
+ def register_container(self, c: PyntContainer):
174
+ self.containers.append(c)
175
+
176
+ def stop_all_containers(self):
177
+ for c in self.containers:
178
+ c.stop()
@@ -0,0 +1 @@
1
+ from .store import CredStore
@@ -0,0 +1,23 @@
1
+ import json
2
+
3
+ from .store_connector import StoreConnector
4
+
5
+ class JsonStoreConnector(StoreConnector):
6
+ def __init__(self, data) -> None:
7
+ if not data:
8
+ data = JsonStoreConnector.default_value()
9
+ super().__init__(data)
10
+ self.json = json.loads(self._data)
11
+
12
+ def get(self, key):
13
+ return self.json.get(key, None)
14
+
15
+ def put(self, key, value):
16
+ self.json[key] = value
17
+
18
+ def dump(self):
19
+ return json.dumps(self.json, indent=2)
20
+
21
+ @staticmethod
22
+ def default_value():
23
+ return json.dumps({})
@@ -0,0 +1,55 @@
1
+ from typing import Type
2
+ import os
3
+ import platform
4
+
5
+ from .json_connector import JsonStoreConnector
6
+ from .store_connector import StoreConnector
7
+
8
+ class Store():
9
+ def __init__(self, file_location: str, connector_type: Type[StoreConnector]) -> None :
10
+ self.file_location = file_location
11
+ self.connector:StoreConnector = None
12
+ self._file = None
13
+ self._connector_tpye = connector_type
14
+
15
+ def _get_file_data(self):
16
+ if self.connector:
17
+ return
18
+
19
+ dirname = os.path.dirname(self.file_location)
20
+ if not os.path.exists(dirname):
21
+ os.makedirs(dirname)
22
+
23
+ if not os.path.exists(self.file_location):
24
+ with open(self.file_location, "w") as f:
25
+ self.connector = self._connector_tpye(self._connector_tpye.default_value())
26
+ return
27
+
28
+ with open(self.file_location, "r+") as f:
29
+ self.connector = self._connector_tpye(f.read())
30
+
31
+ def get(self, key):
32
+ self._get_file_data()
33
+ return self.connector.get(key)
34
+
35
+ def put(self, key, value):
36
+ self._get_file_data()
37
+ self.connector.put(key, value)
38
+
39
+ def get_path(self):
40
+ return self.file_location
41
+
42
+ def __enter__(self):
43
+ self._get_file_data()
44
+ return self
45
+
46
+ def __exit__(self, type, value, traceback):
47
+ with open(self.file_location, "w") as f:
48
+ f.write(self.connector.dump())
49
+
50
+ class CredStore(Store):
51
+ def __init__(self) -> None:
52
+ dir = ".pynt"
53
+ super().__init__(file_location=os.path.join(os.path.expanduser("~"), dir, "creds.json"),
54
+ connector_type=JsonStoreConnector)
55
+
@@ -0,0 +1,17 @@
1
+ class StoreConnector():
2
+ def __init__(self, data) -> None:
3
+ self._data = data
4
+
5
+ def get(self, key):
6
+ raise NotImplemented()
7
+
8
+ def put(self, key, value):
9
+ raise NotImplementedError()
10
+
11
+ def dump(self):
12
+ raise NotImplementedError()
13
+
14
+ @classmethod
15
+ def default_value(cls):
16
+ raise NotImplementedError()
17
+
File without changes
@@ -0,0 +1,31 @@
1
+ import websocket
2
+
3
+ def connect_progress_ws(address):
4
+ ws = websocket.WebSocket()
5
+ ws.connect(address)
6
+ return ws
7
+
8
+ def wrap_ws_progress(ws):
9
+ prev = 0
10
+ while ws.connected:
11
+ try:
12
+ current = int(ws.recv())
13
+ yield current - prev
14
+ prev = current
15
+ except websocket.WebSocketConnectionClosedException:
16
+ return
17
+
18
+ class PyntProgress():
19
+ def __init__(self, what_to_track, total, description) -> None:
20
+ self.running = False
21
+ self.trackable = what_to_track
22
+ self.total = total
23
+ self.description = description
24
+
25
+ def __enter__(self):
26
+ return self
27
+
28
+ def __exit__(self, exc_type, exc_value, exc_traceback):
29
+ self.running = False
30
+
31
+
@@ -0,0 +1,168 @@
1
+ from threading import Thread
2
+ import queue
3
+ import os
4
+ import time
5
+ from rich.text import Text
6
+ from rich.console import Console
7
+ from rich.status import Status
8
+ from rich.progress import Progress
9
+ from typing import Tuple
10
+
11
+ from pyntcli import __version__ as cli_version
12
+ from pyntcli.ui.progress import PyntProgress
13
+
14
+ class PrinterText():
15
+ DEFAULT = 0
16
+ HEADER = 1
17
+ INFO = 2
18
+ WARNING = 3
19
+
20
+ def __init__(self,text,style=DEFAULT):
21
+ self.text = Text(text, PrinterText.get_style(style))
22
+
23
+ @staticmethod
24
+ def get_style(style):
25
+ if style == PrinterText.INFO:
26
+ return "bold blue"
27
+ if style == PrinterText.WARNING:
28
+ return "bold red"
29
+ if style == PrinterText.HEADER:
30
+ return "bold"
31
+ if style == PrinterText.DEFAULT:
32
+ return None
33
+
34
+ def with_line(self, line, style=DEFAULT):
35
+ self.text.append(os.linesep)
36
+ self.text.append(Text(line, PrinterText.get_style(style)))
37
+ return self
38
+
39
+ class AnsiText():
40
+ def __init__(self, data) -> None:
41
+ self.data = data
42
+
43
+ @staticmethod
44
+ def wrap_gen(gen):
45
+ for v in gen:
46
+ yield AnsiText(v)
47
+
48
+ class Spinner():
49
+ def __init__(self, prompt, style) -> None:
50
+ self.prompt = prompt
51
+ self.style = style
52
+ self.runnning = False
53
+
54
+ def __enter__(self):
55
+ return self
56
+
57
+ def __exit__(self, exc_type, exc_value, exc_traceback):
58
+ self.running = False
59
+
60
+ def pynt_version()-> Tuple[str,int]:
61
+ return "Pynt CLI version " + cli_version,PrinterText.DEFAULT
62
+
63
+ def pynt_header()-> Tuple[str,int]:
64
+ return "API Security testing autopilot",PrinterText.DEFAULT
65
+
66
+ def gen_func_loop(gen):
67
+ for l in gen:
68
+ data = l
69
+ if type(data) == bytes:
70
+ data = l.decode("utf-8")
71
+ if not isinstance(data, AnsiText) and data[-1] == "\n":
72
+ data = data[:-1]
73
+ _print(data)
74
+
75
+ def print_generator(gen):
76
+ t = Thread(target=gen_func_loop, args=(gen,), daemon=True)
77
+ t.start()
78
+
79
+ def _print(s):
80
+ Printer.instance().print(s)
81
+
82
+ def print(s):
83
+ if type(s) == bytes:
84
+ s = s.decode("utf-8")
85
+ _print(s)
86
+
87
+ def stop():
88
+ Printer.instance().stop()
89
+
90
+ class Printer():
91
+ _instace = None
92
+
93
+ def __init__(self) -> None:
94
+ self.running = False
95
+ self.run_thread = Thread(target=self._print_in_loop, daemon=True)
96
+ self.print_queue = queue.Queue()
97
+ self.console = Console(tab_size=4)
98
+
99
+ @staticmethod
100
+ def instance():
101
+ if not Printer._instace:
102
+ Printer._instace = Printer()
103
+ Printer._instace.start()
104
+
105
+ return Printer._instace
106
+
107
+ def start(self):
108
+ self.running = True
109
+ self.run_thread.start()
110
+
111
+ def _handle_spinner(self, spinner):
112
+ spinner.running = True
113
+ s = Status(spinner.prompt, spinner=spinner.style, console=self.console)
114
+ s.start()
115
+ while spinner.running and self.running:
116
+ time.sleep(0.5)
117
+ s.stop()
118
+
119
+ def _handle_progress(self, progress):
120
+ progress.running = True
121
+ with Progress(console=self.console, transient=True) as p:
122
+ t = p.add_task(description=progress.description, total=progress.total)
123
+ for update in progress.trackable:
124
+ if not (progress.running and self.running):
125
+ return
126
+ p.update(t, advance=update)
127
+ time.sleep(0.5)
128
+
129
+ def _print_in_loop(self):
130
+ while self.running:
131
+ try:
132
+ data = self.print_queue.get(timeout=1)
133
+ if isinstance(data, list):
134
+ data = data[0]
135
+ if isinstance(data, Spinner):
136
+ self._handle_spinner(data)
137
+ if isinstance(data, PyntProgress):
138
+ self._handle_progress(data)
139
+ else:
140
+ self.console.print(data)
141
+ except queue.Empty:
142
+ pass
143
+
144
+ while not self.print_queue.empty():
145
+ self.console.print(self.print_queue.get())
146
+
147
+
148
+ def print(self, data):
149
+ if isinstance(data,PrinterText):
150
+ data = data.text
151
+ if isinstance(data, AnsiText):
152
+ data = Text.from_ansi(data.data.decode())
153
+ self.print_queue.put(data)
154
+
155
+ def stop(self):
156
+ self.running = False
157
+ self.run_thread.join()
158
+
159
+ def spinner(prompt, style):
160
+ s = Spinner(prompt, style)
161
+ _print([s])
162
+ return s
163
+
164
+ def progress(what_to_track, description, total=100):
165
+ p = PyntProgress(what_to_track, total, description)
166
+ _print([p])
167
+ return p
168
+