osbot-utils 2.70.0__py3-none-any.whl → 2.72.0__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.
@@ -1,19 +1,22 @@
1
1
  import re
2
- from typing import Optional
3
- from osbot_utils.type_safe.Type_Safe__Primitive import Type_Safe__Primitive
2
+ from typing import Optional
3
+ from osbot_utils.helpers.safe_str.schemas.Enum__Safe_Str__Regex_Mode import Enum__Safe_Str__Regex_Mode
4
+ from osbot_utils.type_safe.Type_Safe__Primitive import Type_Safe__Primitive
4
5
 
5
6
  TYPE_SAFE__STR__REGEX__SAFE_STR = re.compile(r'[^a-zA-Z0-9]') # Only allow alphanumerics and numbers
6
7
  TYPE_SAFE__STR__MAX_LENGTH = 512
7
8
 
9
+
8
10
  class Safe_Str(Type_Safe__Primitive, str):
9
- max_length : int = TYPE_SAFE__STR__MAX_LENGTH
10
- regex : re.Pattern = TYPE_SAFE__STR__REGEX__SAFE_STR
11
- replacement_char : str = '_'
12
- allow_empty : bool = True
13
- trim_whitespace : bool = False
14
- allow_all_replacement_char: bool = True
15
- strict_validation : bool = False # If True, don't replace invalid chars, raise an error instead
16
- exact_length : bool = False # If True, require exact length match, not just max length
11
+ max_length : int = TYPE_SAFE__STR__MAX_LENGTH
12
+ regex : re.Pattern = TYPE_SAFE__STR__REGEX__SAFE_STR
13
+ regex_mode : Enum__Safe_Str__Regex_Mode = Enum__Safe_Str__Regex_Mode.REPLACE
14
+ replacement_char : str = '_'
15
+ allow_empty : bool = True
16
+ trim_whitespace : bool = False
17
+ allow_all_replacement_char: bool = True
18
+ strict_validation : bool = False # If True, don't replace invalid chars, raise an error instead
19
+ exact_length : bool = False # If True, require exact length match, not just max length
17
20
 
18
21
 
19
22
  def __new__(cls, value: Optional[str] = None) -> 'Safe_Str':
@@ -41,14 +44,32 @@ class Safe_Str(Type_Safe__Primitive, str):
41
44
  if cls.allow_empty and value =='':
42
45
  return str.__new__(cls, '')
43
46
 
44
- if cls.strict_validation: # If using strict validation, check if the value matches the regex pattern exactly
45
- if not cls.regex.search(value) is None: # If there are non-matching characters
46
- raise ValueError(f"Value contains invalid characters (must match pattern: {cls.regex.pattern})")
47
- sanitized_value = value
47
+ sanitized_value = cls.validate_and_sanitize(value)
48
+
49
+
50
+ return str.__new__(cls, sanitized_value)
51
+
52
+ @classmethod
53
+ def validate_and_sanitize(cls, value):
54
+ if cls.strict_validation:
55
+ if cls.regex_mode == Enum__Safe_Str__Regex_Mode.MATCH: # For 'match' mode, regex defines the valid pattern (like version numbers)
56
+ if not cls.regex.match(value):
57
+ raise ValueError(f"Value does not match required pattern: {cls.regex.pattern}")
58
+ return value
59
+ elif cls.regex_mode == Enum__Safe_Str__Regex_Mode.REPLACE: # For 'replace' mode, regex defines invalid characters to replace
60
+ if cls.regex.search(value) is not None:
61
+ raise ValueError(f"Value contains invalid characters (must not match pattern: {cls.regex.pattern})")
62
+ return value
63
+ else:
64
+ raise ValueError(f"in {cls.__name__}, regex_mode value cannot be None when strict_validation is True")
48
65
  else:
49
- sanitized_value = cls.regex.sub(cls.replacement_char, value) # Apply regex sanitization
66
+ if cls.regex_mode == Enum__Safe_Str__Regex_Mode.MATCH: # Cannot do replacement when regex defines valid pattern
67
+ raise ValueError(f"Cannot use regex_mode='match' without strict_validation=True")
68
+ else: # assume the default Enum__Safe_Str__Regex_Mode.MATCH
69
+ sanitized_value = cls.regex.sub(cls.replacement_char, value)
50
70
 
51
- if not cls.allow_all_replacement_char and set(sanitized_value) == {cls.replacement_char} and sanitized_value: # Check if sanitized value consists entirely of replacement characters
52
- raise ValueError(f"Sanitized value consists entirely of '{cls.replacement_char}' characters")
71
+ if not cls.allow_all_replacement_char and set(sanitized_value) == {
72
+ cls.replacement_char} and sanitized_value:
73
+ raise ValueError(f"Sanitized value consists entirely of '{cls.replacement_char}' characters")
53
74
 
54
- return str.__new__(cls, sanitized_value)
75
+ return sanitized_value
@@ -0,0 +1,20 @@
1
+ import re
2
+ from osbot_utils.helpers.safe_str.Safe_Str import Safe_Str
3
+ from osbot_utils.helpers.safe_str.schemas.Enum__Safe_Str__Regex_Mode import Enum__Safe_Str__Regex_Mode
4
+
5
+ TYPE_SAFE_STR__VERSION__REGEX = re.compile(r'^v(\d{1,3})\.(\d{1,3})\.(\d{1,3})$') # Regex to match versions like v0.1.1 or v999.999.999
6
+ TYPE_SAFE_STR__VERSION__MAX_LENGTH = 12 # Max length for 'v999.999.999'
7
+
8
+ class Safe_Str__Version(Safe_Str):
9
+ regex = TYPE_SAFE_STR__VERSION__REGEX
10
+ regex_mode = Enum__Safe_Str__Regex_Mode.MATCH # in this case we need an exact match of the version regex
11
+ max_length = TYPE_SAFE_STR__VERSION__MAX_LENGTH
12
+ allow_empty = False
13
+ trim_whitespace = True
14
+ strict_validation = True # Ensure the value exactly matches the regex
15
+
16
+ def __add__(self, other): # Concatenation returns regular str, not Safe_Str__Version
17
+ return str.__add__(self, other)
18
+
19
+ def __radd__(self, other): # Reverse concatenation also returns regular str
20
+ return str.__add__(other, self)
@@ -0,0 +1,29 @@
1
+ import ipaddress
2
+ from osbot_utils.type_safe.Type_Safe__Primitive import Type_Safe__Primitive
3
+
4
+
5
+ class Safe_Str__IP_Address(Type_Safe__Primitive, str):
6
+
7
+ def __new__(cls, value: str = None):
8
+ if value is not None: # check that it is not None
9
+ if not isinstance(value, str): # check that it is a string
10
+ raise TypeError(f"Value provided must be a string, and it was {type(value)}")
11
+ else:
12
+ value = value.strip() # trim/strip the value since there could be some leading spaces (which are easy to fix here)
13
+
14
+ if not value: # allow empty or null values
15
+ return str.__new__(cls, "")
16
+
17
+ try:
18
+ ip_obj = ipaddress.ip_address(value) # validate IP address using ipaddress module
19
+ value = str(ip_obj) # Use the canonical representation
20
+ except ValueError as e:
21
+ raise ValueError(f"Invalid IP address: {value}") from e
22
+
23
+ return str.__new__(cls, value)
24
+
25
+ def __add__(self, other): # Concatenation returns regular str, not Safe_Str__IP_Address
26
+ return str.__add__(self, other)
27
+
28
+ def __radd__(self, other): # Reverse concatenation also returns regular str"""
29
+ return str.__add__(other, self)
@@ -0,0 +1,5 @@
1
+ from enum import Enum
2
+
3
+ class Enum__Safe_Str__Regex_Mode(Enum):
4
+ REPLACE : str = 'replace'
5
+ MATCH : str = 'match'
File without changes
@@ -1,6 +1,6 @@
1
1
  import os
2
2
  from osbot_utils.type_safe.Type_Safe import Type_Safe
3
- from osbot_utils.utils.Env import del_env, set_env
3
+ from osbot_utils.utils.Env import del_env, set_env
4
4
 
5
5
 
6
6
  class Temp_Env_Vars(Type_Safe):
@@ -1,14 +1,11 @@
1
- from contextlib import contextmanager
2
- from functools import partial
3
- from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
4
- from threading import Thread
5
- from urllib.parse import urljoin
6
-
7
- from osbot_utils.utils.Files import file_create, path_combine, temp_filename, file_create_all_parent_folders
8
-
9
- from osbot_utils.utils.Misc import random_port, random_string
10
-
11
- from osbot_utils.utils.Http import port_is_open, GET
1
+ from contextlib import contextmanager
2
+ from functools import partial
3
+ from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
4
+ from threading import Thread
5
+ from urllib.parse import urljoin
6
+ from osbot_utils.utils.Files import file_create, path_combine, temp_filename, file_create_all_parent_folders
7
+ from osbot_utils.utils.Misc import random_port, random_string
8
+ from osbot_utils.utils.Http import port_is_open, GET
12
9
 
13
10
 
14
11
  class Temp_Web_Server:
@@ -67,6 +64,7 @@ class Temp_Web_Server:
67
64
  self.server_thread.join()
68
65
  else:
69
66
  self.server._BaseServer__shutdown_request = True # simulate what happens inside self.server.shutdown()
67
+ return self
70
68
 
71
69
  def start(self):
72
70
  if self.http_handler is SimpleHTTPRequestHandler:
@@ -76,6 +74,7 @@ class Temp_Web_Server:
76
74
  self.server = ThreadingHTTPServer((self.host, self.port), handler_config)
77
75
  self.server_thread = Thread(target=self.server.serve_forever, name=self.server_name)
78
76
  self.server_thread.start()
77
+ return self
79
78
 
80
79
  def url(self,path=''):
81
80
  base_url = f"http://{self.host}:{self.port}"
osbot_utils/utils/Http.py CHANGED
@@ -1,20 +1,15 @@
1
1
  import json
2
- import os
3
2
  import re
4
3
  import socket
5
4
  import ssl
6
5
  import unicodedata
7
- from http.cookies import SimpleCookie
8
- from time import sleep
9
- from urllib.parse import quote, urljoin, urlparse
10
- from urllib.request import Request, urlopen
11
-
12
- from osbot_utils.utils.Str import html_decode
13
-
14
- from osbot_utils.utils.Misc import url_decode
15
-
16
- from osbot_utils.utils.Files import save_bytes_as_file, file_size, file_bytes, file_open_bytes, file_create
17
- from osbot_utils.utils.Python_Logger import Python_Logger
6
+ from http.cookies import SimpleCookie
7
+ from time import sleep
8
+ from urllib.parse import quote, urljoin, urlparse
9
+ from urllib.request import Request, urlopen
10
+ from osbot_utils.utils.Str import html_decode
11
+ from osbot_utils.utils.Misc import url_decode
12
+ from osbot_utils.utils.Files import save_bytes_as_file, file_create
18
13
 
19
14
  URL_CHECK_HOST_ONLINE = 'https://www.google.com'
20
15
  URL_JOIN_SAFE__MAX_ITERATIONS = 5
@@ -2,7 +2,9 @@
2
2
  from types import SimpleNamespace
3
3
 
4
4
  class __(SimpleNamespace):
5
- pass
5
+
6
+ def __enter__(self) : return self
7
+ def __exit__(self, exc_type, exc_val, exc_tb): return False
6
8
 
7
9
  def base_classes(cls):
8
10
  if type(cls) is type:
osbot_utils/version CHANGED
@@ -1 +1 @@
1
- v2.70.0
1
+ v2.72.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: osbot_utils
3
- Version: 2.70.0
3
+ Version: 2.72.0
4
4
  Summary: OWASP Security Bot - Utils
5
5
  License: MIT
6
6
  Author: Dinis Cruz
@@ -23,7 +23,7 @@ Description-Content-Type: text/markdown
23
23
 
24
24
  Powerful Python util methods and classes that simplify common apis and tasks.
25
25
 
26
- ![Current Release](https://img.shields.io/badge/release-v2.70.0-blue)
26
+ ![Current Release](https://img.shields.io/badge/release-v2.72.0-blue)
27
27
  [![codecov](https://codecov.io/gh/owasp-sbot/OSBot-Utils/graph/badge.svg?token=GNVW0COX1N)](https://codecov.io/gh/owasp-sbot/OSBot-Utils)
28
28
 
29
29
 
@@ -261,20 +261,24 @@ osbot_utils/helpers/safe_int/Safe_UInt__FileSize.py,sha256=pj1_Gf48JVXbnnvx5-Yqb
261
261
  osbot_utils/helpers/safe_int/Safe_UInt__Percentage.py,sha256=Ck-jiu6NK57Y3ruAjIJ0k-maiKcB2Q7M3t9nKf-8ga8,357
262
262
  osbot_utils/helpers/safe_int/Safe_UInt__Port.py,sha256=uISrh8VKXiEQULQ1POU9YK8Di6z_vr0HWjCTpjA0YaY,482
263
263
  osbot_utils/helpers/safe_int/__init__.py,sha256=kMU2WMsdQmayBEZugxnJV_wRW3O90bc118sx6iIm_mQ,310
264
- osbot_utils/helpers/safe_str/Safe_Str.py,sha256=qR2RDh5cehP_wDDeV5TQahNBwmfcizPFyStALBKD5dw,3509
264
+ osbot_utils/helpers/safe_str/Safe_Str.py,sha256=lVrji7bTDzJIRr20tjsKLEZsKVFrx5AlCQnj-pIe32k,4616
265
265
  osbot_utils/helpers/safe_str/Safe_Str__File__Name.py,sha256=9Evl4P45GCyyR2ywze29GzmqWhyTCcgI-o2CItMsOHo,358
266
266
  osbot_utils/helpers/safe_str/Safe_Str__File__Path.py,sha256=K0yBcvH_Ncqiw7tMqjGqaNyWQh1Zs9qxZ-TR8nEIAow,550
267
267
  osbot_utils/helpers/safe_str/Safe_Str__Hash.py,sha256=IpYdYwXey5WZWa6QfysmsiDP-4L-wP-_EKbkeXhFRH4,1252
268
268
  osbot_utils/helpers/safe_str/Safe_Str__Text.py,sha256=QxuWqF8hNYdOPDn3Yac86h_1ZaX-THbTBDakberiJcs,313
269
269
  osbot_utils/helpers/safe_str/Safe_Str__Text__Dangerous.py,sha256=6-fkXBuWgjz70eQicD07f38eEuKqNzJzaFFY6hozhNQ,388
270
270
  osbot_utils/helpers/safe_str/Safe_Str__Url.py,sha256=4l46AAe5Dy_P_W6d-_3pwtXuehG_Wczq1dC5sOOzg_g,549
271
+ osbot_utils/helpers/safe_str/Safe_Str__Version.py,sha256=atUpMDUbDQWUXUHKkmeMEqSlZGkhNgVInDiOXXQV3kM,1387
271
272
  osbot_utils/helpers/safe_str/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
272
273
  osbot_utils/helpers/safe_str/http/Safe_Str__Html.py,sha256=4_UxGEIkqV_Zeh77nXoO2OUy5SS6gvN3xSwA32DyWs0,662
273
274
  osbot_utils/helpers/safe_str/http/Safe_Str__Http__Content_Type.py,sha256=HURT4n17LmKbcqODsNwhu_D0lI5LpUL8I5t9aqGAZhQ,516
274
275
  osbot_utils/helpers/safe_str/http/Safe_Str__Http__ETag.py,sha256=5cAfIqebraUnVKZJq3CzXzsFdHGy06TznO36Dc6b5A0,477
275
276
  osbot_utils/helpers/safe_str/http/Safe_Str__Http__Last_Modified.py,sha256=FBcPM4h3ldN0F_cSISGZgdidWpjKLCOOPq9sWVGUCzg,438
276
277
  osbot_utils/helpers/safe_str/http/Safe_Str__Http__Text.py,sha256=w0UPkVnQz41BN9asgDUZ4q6pSQUtzNa-4Sjcl56WTCM,1788
278
+ osbot_utils/helpers/safe_str/http/Safe_Str__IP_Address.py,sha256=wOcEt0adsE6-CX08erlRYyGyOLoe4QRaAYdI6sKyKlM,1672
277
279
  osbot_utils/helpers/safe_str/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
280
+ osbot_utils/helpers/safe_str/schemas/Enum__Safe_Str__Regex_Mode.py,sha256=15y_afYIf_LsweutaVVdIJ0qClbVITJGWNEqfRKapNI,120
281
+ osbot_utils/helpers/safe_str/schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
278
282
  osbot_utils/helpers/sqlite/Capture_Sqlite_Error.py,sha256=GSuRYgs1yKQjxMszPoaI7fsfMfuUqhb64AaIysRE6Cs,1747
279
283
  osbot_utils/helpers/sqlite/Sqlite__Cursor.py,sha256=4Im0pCOiERX6Nnf6iagRlSvTR3CPjyPVkdx4NMQV0P0,3342
280
284
  osbot_utils/helpers/sqlite/Sqlite__Database.py,sha256=ORjRUD-xSvf89kDMQ70q7wBlbV5pdKtDerjE6gDN_fg,5616
@@ -357,11 +361,11 @@ osbot_utils/testing/Profiler.py,sha256=4em6Lpp0ONRDoDDCZsc_CdAOi_QolKOp4eA7KHN96
357
361
  osbot_utils/testing/Pytest.py,sha256=R3qdsIXGcNQcu7iobz0RB8AhbbHhc6t757tZoSZRrxA,730
358
362
  osbot_utils/testing/Stderr.py,sha256=ynf0Wle9NvgneLChzAxFBQ0QlE5sbri_fzJ8bEJMNkc,718
359
363
  osbot_utils/testing/Stdout.py,sha256=Gmxd_dOplXlucdSbOhYhka9sWP-Hmqb7ZuLs_JjtW7Y,592
360
- osbot_utils/testing/Temp_Env_Vars.py,sha256=3HK_miVX8fya7y9kYgkVwWmoF-PfJpUUOkwatFkK3QI,930
364
+ osbot_utils/testing/Temp_Env_Vars.py,sha256=C1jHGtessu4Q3_EJCkkOsQWSvKPFcilVFsCxYWKSDLI,927
361
365
  osbot_utils/testing/Temp_File.py,sha256=yZBL9MmcNU4PCQ4xlF4rSss4GylKoX3T_AJF-BlQhdI,1693
362
366
  osbot_utils/testing/Temp_Folder.py,sha256=Dbcohr2ciex6w-kB79R41Nuoa0pgpDbKtPGnlMmJ73k,5194
363
367
  osbot_utils/testing/Temp_Sys_Path.py,sha256=gOMD-7dQYQlejoDYUqsrmuZQ9DLC07ymPZB3zYuNmG4,256
364
- osbot_utils/testing/Temp_Web_Server.py,sha256=0A-gZsd0_3wRj2YuBEOWyV2rhT6dcS2BlArngPXGTtk,3186
368
+ osbot_utils/testing/Temp_Web_Server.py,sha256=pdEshrdoCQWmP962pgPm8JbPG3VQNjH_Wk4rDk1JKZM,3313
365
369
  osbot_utils/testing/Temp_Zip.py,sha256=gppbJchk4tw_bu-7Vt6iJS9mGxeCvNNMMDzeVKHqRv8,1489
366
370
  osbot_utils/testing/Temp_Zip_In_Memory.py,sha256=ibDIWt3K4CX558PbkLbX3InHyWYZcwQwajFm1kAPW5U,3284
367
371
  osbot_utils/testing/Unit_Test.py,sha256=MReR_wDGbbXFDPz7cmuGflcTxRB6TBnO9mYqRpSq8Pk,1304
@@ -416,13 +420,13 @@ osbot_utils/utils/Env.py,sha256=rBksAy6k-J5oAJp-S_JedVlcj1b2VK8V3zsQbacopMc,6076
416
420
  osbot_utils/utils/Exceptions.py,sha256=KyOUHkXQ_6jDTq04Xm261dbEZuRidtsM4dgzNwSG8-8,389
417
421
  osbot_utils/utils/Files.py,sha256=Zg8TV8RpKv3ytnZvvT17DWeEJCisSkO8zzyP_Twhcww,23449
418
422
  osbot_utils/utils/Functions.py,sha256=VoTrAbCHt6hulz6hVz3co8w2xoOS8wE04wyHc5_cC1c,3671
419
- osbot_utils/utils/Http.py,sha256=pLDwq0Jd4Zmpps0gEzXTbeycSFRXMN8W-DprNpYq9A0,8189
423
+ osbot_utils/utils/Http.py,sha256=cbymd0YJjflI0K0gfq_ie4Speq-Ugko753VCSlYZuf4,8101
420
424
  osbot_utils/utils/Int.py,sha256=PmlUdU4lSwf4gJdmTVdqclulkEp7KPCVUDO6AcISMF4,116
421
425
  osbot_utils/utils/Json.py,sha256=TvfDoXwOkWzWH-9KMnme5C7iFsMZOleAeue92qmkH6g,8831
422
426
  osbot_utils/utils/Json_Cache.py,sha256=mLPkkDZN-3ZVJiDvV1KBJXILtKkTZ4OepzOsDoBPhWg,2006
423
427
  osbot_utils/utils/Lists.py,sha256=tPz5x5s3sRO97WZ_nsxREBPC5cwaHrhgaYBhsrffTT8,5599
424
428
  osbot_utils/utils/Misc.py,sha256=H_xexJgiTxB3jDeDiW8efGQbO0Zuy8MM0iQ7qXC92JI,17363
425
- osbot_utils/utils/Objects.py,sha256=HFYcTM8o53ongj_Ih5Iw4C043DdHnDg7cOCd20JDG70,13587
429
+ osbot_utils/utils/Objects.py,sha256=iyQ6RojK87iQRoGbh1twNHHy7wSrAro_l4jduly9Do0,13706
426
430
  osbot_utils/utils/Png.py,sha256=V1juGp6wkpPigMJ8HcxrPDIP4bSwu51oNkLI8YqP76Y,1172
427
431
  osbot_utils/utils/Process.py,sha256=lr3CTiEkN3EiBx3ZmzYmTKlQoPdkgZBRjPulMxG-zdo,2357
428
432
  osbot_utils/utils/Python_Logger.py,sha256=M9Oi62LxfnRSlCN8GhaiwiBINvcSdGy39FCWjyDD-Xg,12792
@@ -434,8 +438,8 @@ osbot_utils/utils/Toml.py,sha256=Rxl8gx7mni5CvBAK-Ai02EKw-GwtJdd3yeHT2kMloik,166
434
438
  osbot_utils/utils/Version.py,sha256=Ww6ChwTxqp1QAcxOnztkTicShlcx6fbNsWX5xausHrg,422
435
439
  osbot_utils/utils/Zip.py,sha256=mG42lgTY0tnm14T3P1-DSAIZKkTiYoO3odZ1aOUdc1I,14394
436
440
  osbot_utils/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
437
- osbot_utils/version,sha256=0cHMv8cBSyT1AgEUuKq6G2WmqYGDEd5ktNMn-AhcMBI,8
438
- osbot_utils-2.70.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
439
- osbot_utils-2.70.0.dist-info/METADATA,sha256=-9hKyVUnDUX9X8z-OuMG0sETWVC4RPkILyfo93EC5fQ,1329
440
- osbot_utils-2.70.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
441
- osbot_utils-2.70.0.dist-info/RECORD,,
441
+ osbot_utils/version,sha256=1eUSnZS1NrxKYnn4j42roLYeJRZQpAc7FWBHRLb89U8,8
442
+ osbot_utils-2.72.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
443
+ osbot_utils-2.72.0.dist-info/METADATA,sha256=eVxoScUD2rZxodwibj4wdrTxwWrOfI7MwsNjoEZBaJ0,1329
444
+ osbot_utils-2.72.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
445
+ osbot_utils-2.72.0.dist-info/RECORD,,