Skip to content

Commit

Permalink
Merge dev into main
Browse files Browse the repository at this point in the history
  • Loading branch information
DinisCruz committed Dec 22, 2024
2 parents 7d2a2d7 + dfb0a61 commit cd034bd
Show file tree
Hide file tree
Showing 12 changed files with 139 additions and 11 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Powerful Python util methods and classes that simplify common apis and tasks.

![Current Release](https://img.shields.io/badge/release-v1.87.0-blue)
![Current Release](https://img.shields.io/badge/release-v1.87.1-blue)
[![codecov](https://codecov.io/gh/owasp-sbot/OSBot-Utils/graph/badge.svg?token=GNVW0COX1N)](https://codecov.io/gh/owasp-sbot/OSBot-Utils)


Expand Down
18 changes: 18 additions & 0 deletions osbot_utils/helpers/Guid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import uuid

from osbot_utils.utils.Misc import is_guid

GUID__NAMESPACE = uuid.UUID('2cfec064-537a-4ff7-8fdc-2fc9e2606f3d')

class Guid(str):
def __new__(cls, value: str):
if not isinstance(value, str): # Check if the value is a string
raise ValueError(f'in Guid: value provided was not a string: {value}') # if not raise a ValueError
if is_guid(value):
guid = value
else:
guid = uuid.uuid5(GUID__NAMESPACE, value) # Generate a UUID5 using the namespace and value
return super().__new__(cls, str(guid)) # Return a new instance of Guid initialized with the string version of the UUID

def __str__(self):
return self
2 changes: 1 addition & 1 deletion osbot_utils/helpers/Random_Guid.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ def __new__(cls, value=None):
raise ValueError(f'in Random_Guid: value provided was not a Guid: {value}')

def __str__(self):
return self
return self
4 changes: 2 additions & 2 deletions osbot_utils/helpers/Safe_Id.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
from osbot_utils.utils.Str import safe_id

class Safe_Id(str):
def __new__(cls, value=None):
def __new__(cls, value=None, max_length=36):
if value is None:
value = safe_id(random_id_short('safe-id'))
sanitized_value = safe_id(value)
sanitized_value = safe_id(value, max_length=max_length)
return str.__new__(cls, sanitized_value)

def __str__(self):
Expand Down
22 changes: 22 additions & 0 deletions osbot_utils/helpers/Str_ASCII.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import re

REGEX__ASCII_VALUE = re.compile(r'[^a-zA-Z0-9_\s!@#$%^&*()\[\]{}\-+=:;,.?]')

class Str_ASCII(str):
"""
A string subclass that ensures values only contain safe ASCII characters.
Replaces any unsafe characters with underscores.
"""
def __new__(cls, value=None, max_length=None):
if value is None:
value = ""

if not isinstance(value, str):
value = str(value)

if max_length and len(value) > max_length:
raise ValueError(f"Value length exceeds maximum of {max_length} characters (was {len(value)})")

sanitized_value = REGEX__ASCII_VALUE.sub('_', value)

return super().__new__(cls, sanitized_value)
1 change: 0 additions & 1 deletion osbot_utils/utils/Misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,6 @@ def is_guid(value):
except Exception:
return False


def ignore_warning__unclosed_ssl():
import warnings
warnings.filterwarnings("ignore", category=ResourceWarning, message="unclosed.*<ssl.SSLSocket.*>")
Expand Down
6 changes: 4 additions & 2 deletions osbot_utils/utils/Objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,13 @@ def convert_dict_to_value_from_obj_annotation(target, attr_name, value):

def convert_to_value_from_obj_annotation(target, attr_name, value): # todo: see the side effects of doing this for all ints and floats

from osbot_utils.helpers.Safe_Id import Safe_Id
from osbot_utils.helpers.Guid import Guid
from osbot_utils.helpers.Timestamp_Now import Timestamp_Now
from osbot_utils.helpers.Random_Guid import Random_Guid
from osbot_utils.helpers.Safe_Id import Safe_Id
from osbot_utils.helpers.Str_ASCII import Str_ASCII

TYPE_SAFE__CONVERT_VALUE__SUPPORTED_TYPES = [Safe_Id, Random_Guid, Timestamp_Now]
TYPE_SAFE__CONVERT_VALUE__SUPPORTED_TYPES = [Guid, Random_Guid, Safe_Id, Str_ASCII, Timestamp_Now]

if target is not None and attr_name is not None:
if hasattr(target, '__annotations__'):
Expand Down
4 changes: 2 additions & 2 deletions osbot_utils/utils/Str.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ def strip_quotes(value: str): # Remove surrounding quo
return value[1:-1]
return value

def safe_id(value):
def safe_id(value, max_length=36):
if value is None or value == "":
raise ValueError("Invalid ID: The ID must not be empty.")

if not isinstance(value, str):
value = str(value)

if len(value) > 36:
if len(value) > max_length:
raise ValueError(f"Invalid ID: The ID must not exceed 36 characters (was {len(value)}).")

sanitized_value = REGEX__SAFE_ID_REGEX.sub('_', value)
Expand Down
2 changes: 1 addition & 1 deletion osbot_utils/version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v1.87.0
v1.87.1
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "osbot_utils"
version = "v1.87.0"
version = "v1.87.1"
description = "OWASP Security Bot - Utils"
authors = ["Dinis Cruz <[email protected]>"]
license = "MIT"
Expand Down
41 changes: 41 additions & 0 deletions tests/unit/helpers/test_Guid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from unittest import TestCase
from osbot_utils.helpers.Guid import Guid


class test_Guid(TestCase):
def setUp(self):
self.test_value = "test-guid"
self.guid = Guid(self.test_value)

def test_create_guid(self):
assert isinstance(self.guid, Guid)
assert isinstance(self.guid, str)
assert len(str(self.guid)) == 36 # Standard UUID length

def test_deterministic(self):
guid_1 = Guid(self.test_value)
guid_2 = Guid(self.test_value)
assert guid_1 == guid_2 # Same input produces same UUID
assert guid_1 == '2d68e4f3-bb56-5e6f-a2a6-9c20e650f08a'
assert Guid('a') == '8a809dc1-0090-52e4-bf96-c888f7e20aa5' # static strings always produce the same value
assert Guid('b') == '6e2adca9-5cbe-5d1b-bd74-3ed29cf04fc3'

def test_different_inputs(self):
guid_1 = Guid("value-1")
guid_2 = Guid("value-2")
assert guid_1 != guid_2 # Different inputs produce different UUIDs

def test_empty_input(self):
guid = Guid("") # Empty string is valid
assert isinstance(guid, Guid)
assert len(str(guid)) == 36

def test_none_input(self):
with self.assertRaises(ValueError) as context:
Guid(None) # Non-string input
assert "value provided was not a string" in str(context.exception)

def test_invalid_input(self):
with self.assertRaises(ValueError) as context:
Guid(123) # Non-string input
assert "value provided was not a string" in str(context.exception)
46 changes: 46 additions & 0 deletions tests/unit/helpers/test_Str_ASCII.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from unittest import TestCase
from osbot_utils.helpers.Str_ASCII import Str_ASCII


class test_Str_ASCII(TestCase):

def test_basic_values(self):
test_cases = [("hello-world" , "hello-world" ),
("hello™world" , "hello_world" ),
("hello世界" , "hello__" ),
("[email protected]" , "[email protected]" ),
("multiple___spaces" , "multiple___spaces"),
("_trim_edges_" , "_trim_edges_" ),
(123 , "123" ),
(3.14 , "3.14" )]
for input_value, expected in test_cases:
result = Str_ASCII(input_value)
assert result == expected

def test_max_length(self):
# Valid length
assert Str_ASCII("test", max_length=10) == "test"

# Invalid length
with self.assertRaises(ValueError) as context:
Str_ASCII("too_long_string", max_length=5)
assert "Value length exceeds maximum" in str(context.exception)

def test_handle_invalid_inputs(self):
invalid_cases = [None, # None value
"", # Empty string
"™™™", # Only special chars
"_____" # Only underscores
]
for invalid_input in invalid_cases:
Str_ASCII(invalid_input)

def test_preserves_allowed_chars(self):
special_chars = "!@#$%^&*()[]{}-+=:;,.?"
result = Str_ASCII(f"test{special_chars}123")
assert result == f"test{special_chars}123"

def test_replaces_invalid_chars(self):
invalid_chars = "™£€¥©®℗"
result = Str_ASCII(f"test{invalid_chars}123")
assert result == "test_______123"

0 comments on commit cd034bd

Please sign in to comment.