ark-sdk-python 2.0.0__py3-none-any.whl → 2.0.2__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.
@@ -514,6 +514,7 @@ class ArkPydanticArgparse:
514
514
  'properties' not in schema['properties'][prop_name]
515
515
  and (
516
516
  'additionalProperties' in schema['properties'][prop_name]
517
+ and isinstance(schema['properties'][prop_name]['additionalProperties'], dict)
517
518
  and 'type' in schema['properties'][prop_name]['additionalProperties']
518
519
  and len(schema['properties'][prop_name]['additionalProperties']) == 1
519
520
  )
ark_sdk_python/ark.py CHANGED
@@ -17,7 +17,7 @@ from ark_sdk_python.actions import ArkAction, ArkCacheAction, ArkConfigureAction
17
17
 
18
18
  urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
19
19
 
20
- __version__ = '2.0.0'
20
+ __version__ = '2.0.2'
21
21
 
22
22
 
23
23
  def main():
@@ -10,3 +10,13 @@ class ArkJWTUtils:
10
10
  token,
11
11
  options={'verify_signature': False},
12
12
  )
13
+
14
+ @staticmethod
15
+ def get_subdomain_from_token(token: str) -> str:
16
+ claims = ArkJWTUtils.get_unverified_claims(token)
17
+ return claims.get('subdomain', '')
18
+
19
+ @staticmethod
20
+ def get_platform_domain_from_token(token: str) -> str:
21
+ claims = ArkJWTUtils.get_unverified_claims(token)
22
+ return claims.get('platform_domain', '')
@@ -1,3 +1,4 @@
1
+ from ark_sdk_python.common.connections.ssh.ark_pty_ssh_connection import ArkPTYSSHConnection
1
2
  from ark_sdk_python.common.connections.ssh.ark_ssh_connection import SSH_PORT, ArkSSHConnection
2
3
 
3
- __all__ = ['ArkSSHConnection', 'SSH_PORT']
4
+ __all__ = ['ArkSSHConnection', 'ArkPTYSSHConnection', 'SSH_PORT']
@@ -0,0 +1,142 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from pathlib import Path
5
+ from typing import Any, Optional
6
+
7
+ from overrides import overrides
8
+ from pexpect import pxssh
9
+
10
+ from ark_sdk_python.common.connections.ark_connection import ArkConnection
11
+ from ark_sdk_python.common.connections.ssh.ark_ssh_connection import SSH_PORT
12
+ from ark_sdk_python.models import ArkException
13
+ from ark_sdk_python.models.common.connections import ArkConnectionCommand, ArkConnectionDetails, ArkConnectionResult
14
+
15
+
16
+ class ArkPTYSSHConnection(ArkConnection):
17
+ __ANSI_STRIPPER: Any = re.compile(r'\x1b[^m]*m|\x1b\[\?2004[hl]')
18
+
19
+ def __init__(self):
20
+ super().__init__()
21
+ self.__is_connected: bool = False
22
+ self.__is_suspended: bool = False
23
+ self.__ssh_client: Optional[pxssh.pxssh] = None
24
+
25
+ @overrides
26
+ def connect(self, connection_details: ArkConnectionDetails) -> None:
27
+ """
28
+ Performs SSH connection with given details or keys
29
+ Saves the ssh session to be used for command executions
30
+
31
+ Args:
32
+ connection_details (ArkConnectionDetails): _description_
33
+
34
+ Raises:
35
+ ArkException: _description_
36
+ """
37
+ if self.__is_connected:
38
+ return
39
+ try:
40
+ target_port = SSH_PORT
41
+ user = None
42
+ if connection_details.port:
43
+ target_port = connection_details.port
44
+ credentials_map = {}
45
+ if connection_details.credentials:
46
+ user = connection_details.credentials.user
47
+ if connection_details.credentials.password:
48
+ credentials_map['password'] = connection_details.credentials.password.get_secret_value()
49
+ elif connection_details.credentials.private_key_filepath:
50
+ path = Path(connection_details.credentials.private_key_filepath)
51
+ if not path.exists():
52
+ raise ArkException(f'Given private key path [{path}] does not exist')
53
+ credentials_map['ssh_key'] = connection_details.credentials.private_key_filepath
54
+ self.__ssh_client = pxssh.pxssh()
55
+ self.__ssh_client.login(
56
+ server=connection_details.address,
57
+ username=user,
58
+ port=target_port,
59
+ login_timeout=30,
60
+ **credentials_map,
61
+ )
62
+ self.__is_connected = True
63
+ self.__is_suspended = False
64
+ except Exception as ex:
65
+ raise ArkException(f'Failed to ssh connect [{str(ex)}]') from ex
66
+
67
+ @overrides
68
+ def disconnect(self) -> None:
69
+ """
70
+ Disconnects the ssh session
71
+ """
72
+ if not self.__is_connected:
73
+ return
74
+ self.__ssh_client.logout()
75
+ self.__ssh_client = None
76
+ self.__is_connected = False
77
+ self.__is_suspended = False
78
+
79
+ @overrides
80
+ def suspend_connection(self) -> None:
81
+ """
82
+ Suspends execution of ssh commands
83
+ """
84
+ self.__is_suspended = True
85
+
86
+ @overrides
87
+ def restore_connection(self) -> None:
88
+ """
89
+ Restores execution of ssh commands
90
+ """
91
+ self.__is_suspended = False
92
+
93
+ @overrides
94
+ def is_suspended(self) -> bool:
95
+ """
96
+ Checks whether ssh commands can be executed or not
97
+
98
+ Returns:
99
+ bool: _description_
100
+ """
101
+ return self.__is_suspended
102
+
103
+ @overrides
104
+ def is_connected(self) -> bool:
105
+ """
106
+ Checks whether theres a ssh session connected
107
+
108
+ Returns:
109
+ bool: _description_
110
+ """
111
+ return self.__is_connected
112
+
113
+ @overrides
114
+ def run_command(self, command: ArkConnectionCommand) -> ArkConnectionResult:
115
+ """
116
+ Runs a command over ssh session, returning the result accordingly
117
+
118
+ Args:
119
+ command (ArkConnectionCommand): _description_
120
+
121
+ Raises:
122
+ ArkException: _description_
123
+
124
+ Returns:
125
+ ArkConnectionResult: _description_
126
+ """
127
+ if not self.__is_connected or self.__is_suspended:
128
+ raise ArkException('Cannot run command while not being connected')
129
+ self._logger.debug(f'Running command [{command.command}]')
130
+ self.__ssh_client.sendline(command.command)
131
+ self.__ssh_client.prompt()
132
+ stdout = self.__ssh_client.before.decode()
133
+ self.__ssh_client.sendline('echo $?')
134
+ self.__ssh_client.prompt()
135
+ exit_code_output = self.__ssh_client.before.decode()
136
+ exit_code_output = ArkPTYSSHConnection.__ANSI_STRIPPER.sub('', exit_code_output)
137
+ rc = int(exit_code_output.strip().splitlines()[-1])
138
+ if rc != command.expected_rc and command.raise_on_error:
139
+ raise ArkException(f'Failed to execute command [{command.command}] - [{rc}] - [{stdout}]')
140
+ self._logger.debug(f'Command rc: [{rc}]')
141
+ self._logger.debug(f'Command stdout: [{stdout}]')
142
+ return ArkConnectionResult(stdout=stdout, rc=rc)
@@ -0,0 +1,145 @@
1
+ import argparse
2
+ import tempfile
3
+
4
+ from ark_sdk_python.auth import ArkISPAuth
5
+ from ark_sdk_python.common.ark_jwt_utils import ArkJWTUtils
6
+ from ark_sdk_python.common.connections.ssh.ark_pty_ssh_connection import ArkPTYSSHConnection
7
+ from ark_sdk_python.models.auth import ArkAuthMethod, ArkAuthProfile, ArkSecret
8
+ from ark_sdk_python.models.auth.ark_auth_method import IdentityServiceUserArkAuthMethodSettings
9
+ from ark_sdk_python.models.common.connections.ark_connection_command import ArkConnectionCommand
10
+ from ark_sdk_python.models.common.connections.ark_connection_credentials import ArkConnectionCredentials
11
+ from ark_sdk_python.models.common.connections.ark_connection_details import ArkConnectionDetails
12
+ from ark_sdk_python.models.services.sia.sso.ark_sia_sso_get_ssh_key import ArkSIASSOGetSSHKey
13
+ from ark_sdk_python.services.sia.sso import ArkSIASSOService
14
+
15
+
16
+ def login_to_identity_security_platform(service_user: str, service_token: str, application_name: str) -> ArkISPAuth:
17
+ """
18
+ This will perform login to the tenant with the given service user credentials
19
+ Caching the logged in token is disabled and will perform a full login on each call
20
+ Will return an identity security platform authenticated class
21
+
22
+ Args:
23
+ service_user (str): _description_
24
+ service_token (str): _description_
25
+ application_name (str): _description_
26
+
27
+ Returns:
28
+ ArkISPAuth: _description_
29
+ """
30
+ print('Logging in to the tenant')
31
+ isp_auth = ArkISPAuth(cache_authentication=False)
32
+ isp_auth.authenticate(
33
+ auth_profile=ArkAuthProfile(
34
+ username=service_user,
35
+ auth_method=ArkAuthMethod.IdentityServiceUser,
36
+ auth_method_settings=IdentityServiceUserArkAuthMethodSettings(identity_authorization_application=application_name),
37
+ ),
38
+ secret=ArkSecret(secret=service_token),
39
+ )
40
+ print('Logged in successfully')
41
+ return isp_auth
42
+
43
+
44
+ def construct_ssh_proxy_address(isp_auth: ArkISPAuth) -> str:
45
+ """
46
+ For a given authentication, construct the SSH proxy address
47
+ This'll grab from the ID token of the logged in user the name of the tenant (subdomain)
48
+ And the platform domain (cyberark.cloud)
49
+ And will concatenante everything to construct the SSH proxy address
50
+ will return the constructed ssh proxy address
51
+
52
+ Args:
53
+ isp_auth (ArkISPAuth): _description_
54
+
55
+ Returns:
56
+ str: _description_
57
+ """
58
+ token = isp_auth.token.token.get_secret_value()
59
+ return f'{ArkJWTUtils.get_subdomain_from_token(token)}.ssh.{ArkJWTUtils.get_platform_domain_from_token(token)}'
60
+
61
+
62
+ def generate_mfa_caching_ssh_key(isp_auth: ArkISPAuth, folder: str) -> str:
63
+ """
64
+ This function will use the logged in user
65
+ And generate an SSH key into a given folder
66
+ Will return the path to the SSH key
67
+
68
+ Args:
69
+ isp_auth (ArkISPAuth): _description_
70
+ temp_folder (str): _description_
71
+
72
+ Returns:
73
+ str: _description_
74
+ """
75
+ print('Generating SSH MFA Caching Key')
76
+ sso_service = ArkSIASSOService(isp_auth=isp_auth)
77
+ path = sso_service.short_lived_ssh_key(get_ssh_key=ArkSIASSOGetSSHKey(folder=folder))
78
+ print(f'MFA Caching SSH Key generated to [{path}]')
79
+ return path
80
+
81
+
82
+ def connect_and_validate_connection(ssh_key_path: str, proxy_address: str, connection_string: str, command: str) -> None:
83
+ """
84
+ This function will perform a connection via SSH and via SIA proxy to the target
85
+ For the given proxy address and connection string, a connection will be made with the ssh key
86
+ Once connected, the given command will be performed, expecting a return code of 0 as success
87
+
88
+ Args:
89
+ ssh_key_path (str): _description_
90
+ proxy_address (str): _description_
91
+ connection_string (str): _description_
92
+ command (str): _description_
93
+ """
94
+ print(f'Connecting to proxy [{proxy_address}] and connection string [{connection_string}]')
95
+ ssh_connection = ArkPTYSSHConnection()
96
+ ssh_connection.connect(
97
+ ArkConnectionDetails(
98
+ address=proxy_address,
99
+ credentials=ArkConnectionCredentials(
100
+ user=connection_string,
101
+ private_key_filepath=ssh_key_path,
102
+ ),
103
+ ),
104
+ )
105
+ print(f'Running test command [{command}]')
106
+ ssh_connection.run_command(
107
+ ArkConnectionCommand(command=command, expected_rc=0),
108
+ )
109
+ print('Finished Successfully!')
110
+
111
+
112
+ if __name__ == '__main__':
113
+ # Construct an argument parser for CLI parameters for the script
114
+ parser = argparse.ArgumentParser()
115
+ parser.add_argument('--service-user', required=True, help='Service user to login and perform the operation with')
116
+ parser.add_argument('--service-token', required=True, help='Service user token to use for logging in and connecting')
117
+ parser.add_argument(
118
+ '--service-application',
119
+ default='__idaptive_cybr_user_oidc',
120
+ help='Service application to login to, defaults to cyberark ISP application',
121
+ )
122
+ parser.add_argument(
123
+ '--connection-string',
124
+ required=True,
125
+ help='SSH connection string to use for the connection, without the proxy address.'
126
+ 'A connection string may look like the following'
127
+ '<service-user>#<tenant_subdomain>@<target_user>@<target_address>#<optional_network_name>',
128
+ )
129
+ parser.add_argument('--test-command', default='ls -l', help='Test command to use once connected to the target, defaults to ls -l')
130
+
131
+ # Parse the CLI parameters
132
+ args = parser.parse_args()
133
+
134
+ # Construct a temporary folder to be used for the MFA Caching SSH key generation
135
+ # At the end of the execution, the temporary folder will be deleted alongside the SSH key
136
+ with tempfile.TemporaryDirectory() as temp_folder:
137
+ # Perform the flow:
138
+ # - Login to the tenant
139
+ # - Generate an MFA Caching SSH key
140
+ # - Construct the ssh proxy address
141
+ # - Connect in SSH to the proxy / target with the MFA Caching SSH Key and perform the command
142
+ isp_auth = login_to_identity_security_platform(args.service_user, args.service_token, args.service_application)
143
+ ssh_key_path = generate_mfa_caching_ssh_key(isp_auth, temp_folder)
144
+ proxy_address = construct_ssh_proxy_address(isp_auth)
145
+ connect_and_validate_connection(ssh_key_path, proxy_address, args.connection_string, args.test_command)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: ark-sdk-python
3
- Version: 2.0.0
3
+ Version: 2.0.2
4
4
  Summary: Official Ark SDK / CLI for CyberArk Identity Security Platform
5
5
  License: Apache-2.0
6
6
  Author: CyberArk
@@ -29,6 +29,7 @@ Requires-Dist: keyrings.cryptfile
29
29
  Requires-Dist: overrides
30
30
  Requires-Dist: packaging
31
31
  Requires-Dist: paramiko
32
+ Requires-Dist: pexpect
32
33
  Requires-Dist: progress
33
34
  Requires-Dist: pycryptodome
34
35
  Requires-Dist: pydantic (==2.*)
@@ -9,8 +9,8 @@ ark_sdk_python/actions/ark_profiles_action.py,sha256=luUe-8pmDuzGyBqFcxXZZcLFUrZ
9
9
  ark_sdk_python/actions/ark_service_exec_action.py,sha256=IHIm9oISRThOD8WNJe4oJX-9CTpH8WUuGlXxlcLvNxI,5127
10
10
  ark_sdk_python/args/__init__.py,sha256=mCb4UrIRL4u58QyUbqqH2xAU0VN91pxjK5sa_UwCM_k,279
11
11
  ark_sdk_python/args/ark_args_formatter.py,sha256=-Rs_rGMIr1bprD1SfYlMsLoSOFgr_RFrRdREvbmPU6k,9204
12
- ark_sdk_python/args/ark_pydantic_argparse.py,sha256=zqaYfrPrgRe6b0a7iO4Tuu8O6nJvRPe3-uZuQDihmIk,39981
13
- ark_sdk_python/ark.py,sha256=KQEEb0ZL7cgalWYfKktaEheENhylSugig4BHVX8TDmw,1280
12
+ ark_sdk_python/args/ark_pydantic_argparse.py,sha256=uupNLnUmO5LrTrRZsBv7qRKgDsx4GaJMwOUh1TCc1-w,40083
13
+ ark_sdk_python/ark.py,sha256=eI5sMAgh2sbPrOyb1aqvj3R0GmTdiKD3PQvSokyNVVI,1280
14
14
  ark_sdk_python/ark_api.py,sha256=qEB2j-xf8kiSdAvTXrvEdqGDQ-l1aoEvktxnq7BYNRI,12486
15
15
  ark_sdk_python/auth/__init__.py,sha256=1l29Rf3VhwmV8z3YROiQVcgrZYSeeCqVN4zna77tnlk,620
16
16
  ark_sdk_python/auth/ark_auth.py,sha256=2fPlABM3AwWmb1h0lQPQAz93u1ZluYKv7axlfg4lq5Q,12988
@@ -32,7 +32,7 @@ ark_sdk_python/common/__init__.py,sha256=QpzMfvy7yl_kw5B5uYuQkVhIw_p1QH5Vvm4lHpK
32
32
  ark_sdk_python/common/ark_async_client.py,sha256=GxFxn8hJbVisCyHmTUiVxhEIOCj9j-_ijAZsoSxnvbs,2518
33
33
  ark_sdk_python/common/ark_async_request.py,sha256=03q-U4K6oHJ18UNjQcJWT01plfZDNe74RI7iQqJmQmY,1885
34
34
  ark_sdk_python/common/ark_client.py,sha256=iUoAS8ws-CxDlO1jN0hccuEI0NArMHYGacC0rAlCiho,7802
35
- ark_sdk_python/common/ark_jwt_utils.py,sha256=wueVpV-0q6M9Uo0GNbGKj8AdZ1edKvP2GXUEHUK6FM0,280
35
+ ark_sdk_python/common/ark_jwt_utils.py,sha256=0Qh0HnU3f9t1X2L3juZHrOBWKrmBXAbV7zdsyeoYYvs,638
36
36
  ark_sdk_python/common/ark_keyring.py,sha256=xPmR7zt0sMZF6qWpeG-8irgOQXAX6au-ykIzKv0dSKo,11271
37
37
  ark_sdk_python/common/ark_logger.py,sha256=l06FH2dg6AkpR8M6zVoZVgaiyOZ2G9ANxHNahPDHVEE,2445
38
38
  ark_sdk_python/common/ark_page.py,sha256=4_kycDX8vLloyZOlbM8N9XYXiF2cb5O-l4FbZ5hA7CY,358
@@ -42,7 +42,8 @@ ark_sdk_python/common/ark_retry.py,sha256=fWkKYLIK2z5U14NFzhqUP_o0VkSG9F9EiZIhdf
42
42
  ark_sdk_python/common/ark_system_config.py,sha256=cl44cYSgXZEnoRYu048aBeJwWRPdCT_flC3hTWyytX4,2436
43
43
  ark_sdk_python/common/connections/__init__.py,sha256=RyARha6jTeaUw1cIGBSR7nOYxCV3fC24juKb-17yXLw,104
44
44
  ark_sdk_python/common/connections/ark_connection.py,sha256=wjYpn_9EJ-B679006VqQREFeTOhINmw-heiRqlpuCnY,1613
45
- ark_sdk_python/common/connections/ssh/__init__.py,sha256=kS5Y1oIt4ncax68et2ykPwGA07taVUUpW-saCDiLwBk,140
45
+ ark_sdk_python/common/connections/ssh/__init__.py,sha256=Ycv3BDI2iw-N7BdRkpAv2Qi90O4bp8sPyNsJnuQLfWo,256
46
+ ark_sdk_python/common/connections/ssh/ark_pty_ssh_connection.py,sha256=PRrS3pcVdW6Ike6AWnh5JuOLhcYJC7nJHQOf6VZa_kI,4973
46
47
  ark_sdk_python/common/connections/ssh/ark_ssh_connection.py,sha256=yVtrM_clLScg1uNY-Fextiup59h2AIbKxQQadLfiqpw,5195
47
48
  ark_sdk_python/common/connections/winrm/__init__.py,sha256=TX8jEK5BPwvlzgo8lH4sGDcY9oL593rdEutQL4MMra4,164
48
49
  ark_sdk_python/common/connections/winrm/ark_winrm_connection.py,sha256=O8ifpvmuzFpvqvjb3wFmg576v5jFZwFHi20wlV2UtwE,6854
@@ -56,6 +57,7 @@ ark_sdk_python/examples/create_sia_vm_environment.py,sha256=omk2mb1yrAU8w3mNSqjH
56
57
  ark_sdk_python/examples/default_suffix.py,sha256=GEAnsrVhNdsIZ2YRJd4Xs4HYv86UdmXwPwOs7ajcQIQ,662
57
58
  ark_sdk_python/examples/list_pcloud_accounts.py,sha256=Xzq_LIp_woRgCt73w1AMlXbB6P8yNRHr1VGIBCyQswk,810
58
59
  ark_sdk_python/examples/session_monitoring_activites.py,sha256=WSGISA4Qye8q22WpiHq0HKYTfbwLK7038mRHp2Kr9IQ,1632
60
+ ark_sdk_python/examples/validate_ssh_connection.py,sha256=hSUtmADqupxEkbKRTvC0MKmJBHfe-PYltVP0u8sHuhQ,6357
59
61
  ark_sdk_python/models/__init__.py,sha256=2EKDAGANDadQfJrnTjm2Jc7JnINcZOwCRXWHEXmIH6o,1082
60
62
  ark_sdk_python/models/actions/__init__.py,sha256=2xPZhoHco50bmPX7rm6uPOAcYURoTP9J5KF4E3XNaFA,743
61
63
  ark_sdk_python/models/actions/ark_configure_action_consts.py,sha256=-YNgiGv1GsQx-JvO0eXXrrOlDcH6sRDLdO7kvTHto-M,1143
@@ -477,9 +479,9 @@ ark_sdk_python/services/sia/workspaces/targetsets/__init__.py,sha256=UjHr81LMcMl
477
479
  ark_sdk_python/services/sia/workspaces/targetsets/ark_sia_target_sets_workspace_service.py,sha256=o9_DzskTV94kkfc4m0Qk8lj5FV8rexRa5RzRwfe9Tbo,11254
478
480
  ark_sdk_python/services/sm/__init__.py,sha256=jzKxEkZYOCW9y_byLfd_odae9m2sh1UfjaaKBVySWY0,95
479
481
  ark_sdk_python/services/sm/ark_sm_service.py,sha256=Rh2dFjNz7VMJUnl43hxL07HEZ8_33KrU_lqfUrqUDn4,11517
480
- ark_sdk_python-2.0.0.dist-info/LICENSE.txt,sha256=d4EzYx2apA3-GqrFY23wLtc6UmQNXG05gyLfZ3oWVRs,11358
481
- ark_sdk_python-2.0.0.dist-info/METADATA,sha256=8no-3v6kS7TkN1nbd9utxx7Bk8VTIJLemREC1tcA278,20079
482
- ark_sdk_python-2.0.0.dist-info/NOTICES.md,sha256=cuG6J8u9QVu5ZjYki-McJmoEmi39foN30IxA_2fNnKY,125667
483
- ark_sdk_python-2.0.0.dist-info/WHEEL,sha256=CIaYlaP0GnhA0Mv3ERDC3CMg_oGwHXYV1sPT7AFbDI4,110
484
- ark_sdk_python-2.0.0.dist-info/entry_points.txt,sha256=v5bcuRn8w42ZKQspfvX3AliKmLVO6f7Zb_bma77p3vQ,47
485
- ark_sdk_python-2.0.0.dist-info/RECORD,,
482
+ ark_sdk_python-2.0.2.dist-info/LICENSE.txt,sha256=d4EzYx2apA3-GqrFY23wLtc6UmQNXG05gyLfZ3oWVRs,11358
483
+ ark_sdk_python-2.0.2.dist-info/METADATA,sha256=N2GVS4nUgCu0WWdDFBihVWTYqqBz8ocGXhB0OM8vsLU,20102
484
+ ark_sdk_python-2.0.2.dist-info/NOTICES.md,sha256=cuG6J8u9QVu5ZjYki-McJmoEmi39foN30IxA_2fNnKY,125667
485
+ ark_sdk_python-2.0.2.dist-info/WHEEL,sha256=QymApwX2NnTrOAORdnrvo1YegSC1EN2pJY0mOd9rfdE,110
486
+ ark_sdk_python-2.0.2.dist-info/entry_points.txt,sha256=v5bcuRn8w42ZKQspfvX3AliKmLVO6f7Zb_bma77p3vQ,47
487
+ ark_sdk_python-2.0.2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.1.1
2
+ Generator: poetry-core 2.1.2
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any