Files
jeb-utils/src/jeb_utils/utils.py
T

208 lines
5.6 KiB
Python

# Copyright 2026 jCloud Services GbR
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
from typing import Tuple, Literal, Sequence
import ipaddress
__all__ = [
'is_number',
'int_to_bytes',
'bits_to_byte',
'byte_to_bits',
'get_next_lower_integer_multiple',
'find_nearest_lower_number',
'create_file_if_not_exists',
'is_valid_host',
'is_valid_port',
'validate_address_port'
]
def is_number(string: str, base: int = 10) -> bool:
'''
Checks whether the string is a number.
:param string: The string
:type string: str
:param base: The base
:type base: int
:return: ``True`` if the string is a number, otherwise ``False``
:rtype: bool
'''
if base == 0:
return False
if not string:
return False
try:
int(string, base)
return True
except ValueError:
return False
def int_to_bytes(n: int, signed: bool = False) -> bytes:
'''
Converts the integer into bytes (dynamic length).
:param n: The integer
:type n: int
:param signed: Whether the integer is signed or not.
:type signed: bool
:return: The bytes
:rtype: bytes
'''
n = int(n)
length = (n.bit_length() + 7) // 8
if length == 0:
length = 1
return n.to_bytes(length, signed=signed)
def _bits_to_byte(bits: Tuple[Literal[0, 1], Literal[0, 1], Literal[0, 1], Literal[0, 1], Literal[0, 1], Literal[0, 1], Literal[0, 1], Literal[0, 1]]) -> bytes:
if len(bits) != 8:
raise ValueError('bits must be a tuple of 8 bits')
byte = 0
for bit in bits:
if int(bit) not in (0, 1):
raise ValueError('each bit must be 0 or 1')
byte = (byte << 1) | int(bit)
return byte
def bits_to_byte(*bits: Literal[0, 1]) -> bytes:
'''
Converts the bits into a single byte.
:param bits: The bits
:type bits: int
:raises ValueError: If there are more than 8 bits specified or a bit is neither ``0`` nor ``1``.
:return: The byte
:rtype: bytes
'''
if len(bits) > 8:
raise ValueError('a maximum of 8 bits can be converted to a byte')
while len(bits) < 8:
bits = (0,) + bits
return bytes([_bits_to_byte(bits)])
def byte_to_bits(b: bytes) -> Tuple[Literal[0, 1], Literal[0, 1], Literal[0, 1], Literal[0, 1], Literal[0, 1], Literal[0, 1], Literal[0, 1], Literal[0, 1]]:
'''
Converts the byte to a tuple of bits.
:param b: The byte
:type b: bytes
:raises ValueError: If more than one byte is specified or ``b`` is neither a bytestring nor a bytearray,
:return: The bits
:rtype: tuple
'''
if not isinstance(b, (bytes, bytearray)) or len(b) != 1:
raise ValueError('input must be a single byte')
byte = b[0]
return tuple((byte >> i) & 1 for i in reversed(range(8)))
def get_next_lower_integer_multiple(value: int, multiple: int) -> int:
'''
Returns the next lower integer multiple.
:param value: The value
:type value: int
:param multiple: The factor
:type multiple: int
:return: The next lower integer multiple
:rtype: int
'''
return value - (value % multiple)
def find_nearest_lower_number(number_list: Sequence, target: int | float) -> int | float:
'''
Finds the nearest lower number to the target from the list.
:param number_list: The list
:type number_list: Sequence
:param target: The target
:type target: int | float
:return: The nearest lower number to the target from the list.
:rtype: int | float
'''
nearest = None
for number in number_list:
if number <= target:
if nearest is None or number > nearest:
nearest = number
return nearest
def create_file_if_not_exists(path: str, content: bytes = b''):
'''
Creates a file if it does not exist.
:param path: The file path
:type path: str
:param content: Optional, the content of the file if it is newly created.
:type content: bytes
'''
if not os.path.exists(path):
with open(path, 'wb') as f:
f.write(content)
f.close()
def is_valid_host(host: str) -> bool:
'''
Checks whether the host is a valid IP address.
:param host: The host
:type host: str
:return: ``True`` if the host is a valid IP address, otherwise ``False``
:rtype: bool
'''
try:
ipaddress.ip_address(host)
return True
except ValueError:
pass
def is_valid_port(port: str) -> bool:
'''
Checks whether the port is a valid port number.
:param port: The port
:type port: str
:return: ``True`` if the port is a valid port number, otherwise ``False``
:rtype: bool
'''
if not port.isdigit():
return False
port_num = int(port)
return 1 <= port_num <= 65535
def validate_address_port(addr: str) -> bool:
if ':' not in addr:
return False
if addr.startswith('['):
try:
host, port = addr.rsplit(']:', 1)
host = host[1:]
except ValueError:
return False
else:
host, port = addr.rsplit(':', 1)
return is_valid_host(host) and is_valid_port(port)