Fixed license error

This commit is contained in:
2026-02-23 22:37:13 +01:00
parent 8a21c2e4f0
commit 2f467a4128
6 changed files with 60 additions and 46 deletions
+14
View File
@@ -1,3 +1,17 @@
#!/usr/bin/bash #!/usr/bin/bash
# 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.
scp dist/* jcloud@jcloud-services.ddns.net:/srv/data/jcloud/htdocs/simple/jeb-utils scp dist/* jcloud@jcloud-services.ddns.net:/srv/data/jcloud/htdocs/simple/jeb-utils
+1 -1
View File
@@ -7,4 +7,4 @@ name = "jeb-utils"
version = "0.1.4" version = "0.1.4"
description = "Common utils for JEB client and server." description = "Common utils for JEB client and server."
dependencies = ["cryptography"] dependencies = ["cryptography"]
license = "MIT" license = "Apache-2.0"
+4 -4
View File
@@ -25,7 +25,7 @@ __all__ = [
def load_cert_file(path: str) -> x509.Certificate: def load_cert_file(path: str) -> x509.Certificate:
''' '''
Loads a certificate from a file Loads a certificate from a file
:param path: File path :param path: File path
:type path: str :type path: str
:return: The certificate :return: The certificate
@@ -58,7 +58,7 @@ class Identity:
@property @property
def not_valid_after(self): def not_valid_after(self):
return self.cert.not_valid_after_utc return self.cert.not_valid_after_utc
@property @property
def signature_algorithm(self): def signature_algorithm(self):
return self.cert.signature_hash_algorithm return self.cert.signature_hash_algorithm
@@ -81,8 +81,8 @@ class Verifier:
) )
except: except:
return False return False
if (identity.issuer != self.trusted_ca.subject) and verify_issuer: if (identity.issuer != self.trusted_ca.subject) and verify_issuer:
return False return False
return True return True
+30 -30
View File
@@ -78,7 +78,7 @@ log_end_offsets = {}
def format_topic_partitions(topic_name: str, partitions: set) -> list: def format_topic_partitions(topic_name: str, partitions: set) -> list:
''' '''
Formats the topic partitions. Formats the topic partitions.
:param topic_name: The name of the topic, e. g. ``test-topic`` :param topic_name: The name of the topic, e. g. ``test-topic``
:type topic_name: str :type topic_name: str
:param partitions: The partitions, e. g. ``{1, 2, 4}`` :param partitions: The partitions, e. g. ``{1, 2, 4}``
@@ -92,7 +92,7 @@ def format_topic_partitions(topic_name: str, partitions: set) -> list:
def mktopic(topic_name: str): def mktopic(topic_name: str):
''' '''
Creates a topic. Creates a topic.
:param topic_name: The name of the topic :param topic_name: The name of the topic
:type topic_name: str :type topic_name: str
''' '''
@@ -107,7 +107,7 @@ def mktopic(topic_name: str):
def rmtopic(topic_name: str): def rmtopic(topic_name: str):
''' '''
Removes a topic. Removes a topic.
:param topic_name: The topic name :param topic_name: The topic name
:type topic_name: str :type topic_name: str
@@ -130,7 +130,7 @@ def rmtopic(topic_name: str):
def mktopicpart(topic_name: str, partition_number: int): def mktopicpart(topic_name: str, partition_number: int):
''' '''
Creates a partition of a topic. Creates a partition of a topic.
:param topic_name: The topic name :param topic_name: The topic name
:type topic_name: str :type topic_name: str
:param partition_number: The partition number :param partition_number: The partition number
@@ -153,7 +153,7 @@ def mktopicpart(topic_name: str, partition_number: int):
def rmtopicpart(topic_name: str, partition_number: int): def rmtopicpart(topic_name: str, partition_number: int):
''' '''
Removes a partition of a topic. Removes a partition of a topic.
:param topic_name: The topic name :param topic_name: The topic name
:type topic_name: str :type topic_name: str
:param partition_number: The partition number :param partition_number: The partition number
@@ -194,7 +194,7 @@ def _create_base_segment_files(topics: dict):
def get_segment_base_timestamp(topic: str, segment: int) -> int: def get_segment_base_timestamp(topic: str, segment: int) -> int:
''' '''
Returns the base timestamp of a segment. Returns the base timestamp of a segment.
:param topic: The topic :param topic: The topic
:type topic: str :type topic: str
:param segment: The segment base offset :param segment: The segment base offset
@@ -216,7 +216,7 @@ def get_segment_base_timestamp(topic: str, segment: int) -> int:
timestamp = None timestamp = None
else: else:
timestamp = int.from_bytes(timestamp_bytes) timestamp = int.from_bytes(timestamp_bytes)
return timestamp return timestamp
def get_log_end_offsets(topics: dict): def get_log_end_offsets(topics: dict):
@@ -247,7 +247,7 @@ def get_log_end_offsets(topics: dict):
def init(config: dict = {'segment_max_size': 1024 * 256, 'data_dir': './data'}): def init(config: dict = {'segment_max_size': 1024 * 256, 'data_dir': './data'}):
''' '''
Initializes the module. Calling this function is required before using most of the functions of the module. Initializes the module. Calling this function is required before using most of the functions of the module.
:param config: The configuration :param config: The configuration
:type config: dict :type config: dict
''' '''
@@ -269,7 +269,7 @@ def get_next_lower_index_entry(offset: int):
def validate_topic_partition(partition_number: str | int) -> bool: def validate_topic_partition(partition_number: str | int) -> bool:
''' '''
Validates a topic partition number format. Validates a topic partition number format.
:param partition_number: The partition number :param partition_number: The partition number
:type partition_number: str | int :type partition_number: str | int
@@ -283,16 +283,16 @@ def validate_topic_partition(partition_number: str | int) -> bool:
elif not isinstance(partition_number, int): elif not isinstance(partition_number, int):
return False return False
if not (0 < partition_number < 100): if not (0 < partition_number < 100):
return False return False
return True return True
def validate_topic_name(topic_name: str) -> bool: def validate_topic_name(topic_name: str) -> bool:
''' '''
Validates a topic name. Validates a topic name.
:param topic_name: The topic name :param topic_name: The topic name
:type topic_name: str :type topic_name: str
@@ -301,16 +301,16 @@ def validate_topic_name(topic_name: str) -> bool:
''' '''
if not isinstance(topic_name, str): if not isinstance(topic_name, str):
return False return False
if not _TOPIC_NAME_REGEX.match(topic_name): if not _TOPIC_NAME_REGEX.match(topic_name):
return False return False
if len(topic_name) == 0 or len(topic_name) > 255: if len(topic_name) == 0 or len(topic_name) > 255:
return False return False
if topic_name.startswith('.') or topic_name.endswith('.') or topic_name.startswith('..') or topic_name.endswith('..'): if topic_name.startswith('.') or topic_name.endswith('.') or topic_name.startswith('..') or topic_name.endswith('..'):
return False return False
return True return True
def validate_topic_format(topic: str): def validate_topic_format(topic: str):
@@ -319,11 +319,11 @@ def validate_topic_format(topic: str):
_, formatted_partition_number = topic.split(TOPIC_PARTITION_SEPARATOR) _, formatted_partition_number = topic.split(TOPIC_PARTITION_SEPARATOR)
if len(formatted_partition_number) != 2 or not formatted_partition_number.isdigit(): if len(formatted_partition_number) != 2 or not formatted_partition_number.isdigit():
raise ValueError('invalid partition number format') raise ValueError('invalid partition number format')
def check_topic_exists(topic: str): def check_topic_exists(topic: str):
''' '''
Checks whether a topic exists. Checks whether a topic exists.
:param topic: The topic name :param topic: The topic name
:type topic: str :type topic: str
@@ -368,7 +368,7 @@ def _unpack_record_headers(buffer: io.BytesIO, number: int = 256):
key = buffer.read(key_length) key = buffer.read(key_length)
if len(key) < key_length: if len(key) < key_length:
break break
value_length_bytes = buffer.read(4) value_length_bytes = buffer.read(4)
if len(value_length_bytes) < 4: if len(value_length_bytes) < 4:
break break
@@ -384,7 +384,7 @@ def _unpack_record_headers(buffer: io.BytesIO, number: int = 256):
def unpack_record_headers(packed: bytes) -> dict: def unpack_record_headers(packed: bytes) -> dict:
''' '''
Unpacks the record headers from bytes. Unpacks the record headers from bytes.
:param packed: The packed headers :param packed: The packed headers
:type packed: bytes :type packed: bytes
@@ -397,7 +397,7 @@ def unpack_record_headers(packed: bytes) -> dict:
def create_record(topic: str, timestamp: int, record_data: bytes, key: bytes = b'', compression_type: int = 0, headers: dict = {}): def create_record(topic: str, timestamp: int, record_data: bytes, key: bytes = b'', compression_type: int = 0, headers: dict = {}):
''' '''
Creates a record. Creates a record.
:param topic: The topic name :param topic: The topic name
:type topic: str :type topic: str
:param timestamp: The timestamp. :param timestamp: The timestamp.
@@ -416,7 +416,7 @@ def create_record(topic: str, timestamp: int, record_data: bytes, key: bytes = b
topic_name, formatted_partition_number = topic.split(TOPIC_PARTITION_SEPARATOR) topic_name, formatted_partition_number = topic.split(TOPIC_PARTITION_SEPARATOR)
check_topic_exists(topic) check_topic_exists(topic)
topic_dir = f'{DATA_DIR}/topics/{topic_name}{TOPIC_PARTITION_SEPARATOR}{formatted_partition_number}/' topic_dir = f'{DATA_DIR}/topics/{topic_name}{TOPIC_PARTITION_SEPARATOR}{formatted_partition_number}/'
utils.create_file_if_not_exists(topic_dir + '0' * 16 + '.log') utils.create_file_if_not_exists(topic_dir + '0' * 16 + '.log')
segments = sorted([int(f.split('.')[0], 16) for f in os.listdir(topic_dir) if f.endswith('.log') and utils.is_number(f.split('.')[0], 16) and len(f.split('.')[0]) == 16]) segments = sorted([int(f.split('.')[0], 16) for f in os.listdir(topic_dir) if f.endswith('.log') and utils.is_number(f.split('.')[0], 16) and len(f.split('.')[0]) == 16])
@@ -447,7 +447,7 @@ def create_record(topic: str, timestamp: int, record_data: bytes, key: bytes = b
record += key record += key
record += len(value).to_bytes(4) record += len(value).to_bytes(4)
record += value record += value
record = len(record).to_bytes(8) + record # Prepend Record Size record = len(record).to_bytes(8) + record # Prepend Record Size
@@ -460,7 +460,7 @@ def create_record(topic: str, timestamp: int, record_data: bytes, key: bytes = b
utils.create_file_if_not_exists(topic_dir + f'{last_segment:016x}.index') utils.create_file_if_not_exists(topic_dir + f'{last_segment:016x}.index')
utils.create_file_if_not_exists(topic_dir + f'{last_segment:016x}.timeindex') utils.create_file_if_not_exists(topic_dir + f'{last_segment:016x}.timeindex')
if log_end_offsets[topic] % 1024 == 0: if log_end_offsets[topic] % 1024 == 0:
index = log_end_offsets[topic].to_bytes(8) + os.path.getsize(topic_dir + f'{last_segment:016x}.log').to_bytes(4) index = log_end_offsets[topic].to_bytes(8) + os.path.getsize(topic_dir + f'{last_segment:016x}.log').to_bytes(4)
timeindex = timestamp.to_bytes(8) + log_end_offsets[topic].to_bytes(8) timeindex = timestamp.to_bytes(8) + log_end_offsets[topic].to_bytes(8)
@@ -476,7 +476,7 @@ def create_record(topic: str, timestamp: int, record_data: bytes, key: bytes = b
async def fetch_records(topic: str, start_type: int, start: int, max_bytes: int, send_block_function: FunctionType | MethodType): async def fetch_records(topic: str, start_type: int, start: int, max_bytes: int, send_block_function: FunctionType | MethodType):
''' '''
Fetches records from the topic and sends it using ``send_block_function`` Fetches records from the topic and sends it using ``send_block_function``
:param topic: The topic name :param topic: The topic name
:type topic: str :type topic: str
:param start_type: The start type :param start_type: The start type
@@ -509,7 +509,7 @@ async def fetch_records(topic: str, start_type: int, start: int, max_bytes: int,
if len(timestamp_bytes) < 8: if len(timestamp_bytes) < 8:
break break
timestamp = int.from_bytes(timestamp_bytes) timestamp = int.from_bytes(timestamp_bytes)
offset_bytes = f.read(8) offset_bytes = f.read(8)
if len(offset_bytes) < 8: if len(offset_bytes) < 8:
break break
@@ -603,7 +603,7 @@ async def fetch_records(topic: str, start_type: int, start: int, max_bytes: int,
await send_block_function(b'\x00') # EOF await send_block_function(b'\x00') # EOF
class Topic: class Topic:
@@ -612,11 +612,11 @@ class Topic:
def __repr__(self): def __repr__(self):
return f'Topic({self.topic_name})' return f'Topic({self.topic_name})'
@property @property
def segments(self): def segments(self):
return [Segment(self.topic_name, bo) for bo in sorted([int(s.split('.')[0], 16) for s in os.listdir(f'{DATA_DIR}/topics/{self.topic_name}') if s.endswith('.log') and utils.is_number(s.split('.')[0], 16) and len(s.split('.')[0]) == 16])] return [Segment(self.topic_name, bo) for bo in sorted([int(s.split('.')[0], 16) for s in os.listdir(f'{DATA_DIR}/topics/{self.topic_name}') if s.endswith('.log') and utils.is_number(s.split('.')[0], 16) and len(s.split('.')[0]) == 16])]
class Segment: class Segment:
def __init__(self, topic: str, base_offset: int): def __init__(self, topic: str, base_offset: int):
self.topic = topic self.topic = topic
@@ -633,7 +633,7 @@ class Segment:
timestamp = None timestamp = None
else: else:
timestamp = int.from_bytes(timestamp_bytes) timestamp = int.from_bytes(timestamp_bytes)
return timestamp return timestamp
class FileCorruptWarning(UserWarning): ... class FileCorruptWarning(UserWarning): ...
+4 -4
View File
@@ -33,7 +33,7 @@ class MessageFormatError(Exception): ...
async def sendmsg(m: bytes, writer: asyncio.StreamWriter, aesgcm: AESGCM = None, aesnonce: bytes = None, send_headers: bool = True, _content_length: int = None, start_byte: bytes = _MSG_START_BYTE): async def sendmsg(m: bytes, writer: asyncio.StreamWriter, aesgcm: AESGCM = None, aesnonce: bytes = None, send_headers: bool = True, _content_length: int = None, start_byte: bytes = _MSG_START_BYTE):
''' '''
Sends a message. Sends a message.
:param m: The message :param m: The message
:type m: bytes :type m: bytes
:param writer: The writer :param writer: The writer
@@ -66,7 +66,7 @@ async def sendmsg(m: bytes, writer: asyncio.StreamWriter, aesgcm: AESGCM = None,
async def readmsg(reader: asyncio.StreamReader, aesgcm: AESGCM = None, aesnonce: bytes = None, start_byte: bytes = _MSG_START_BYTE) -> bytes: async def readmsg(reader: asyncio.StreamReader, aesgcm: AESGCM = None, aesnonce: bytes = None, start_byte: bytes = _MSG_START_BYTE) -> bytes:
''' '''
Receives a message. Receives a message.
:param reader: The reader :param reader: The reader
:type reader: asyncio.StreamReader :type reader: asyncio.StreamReader
:param aesgcm: AESGCM :param aesgcm: AESGCM
@@ -93,7 +93,7 @@ async def readmsg(reader: asyncio.StreamReader, aesgcm: AESGCM = None, aesnonce:
def pack_fields(*fields: bytes) -> bytes: def pack_fields(*fields: bytes) -> bytes:
''' '''
Packs the fields into a compact bytestring. Packs the fields into a compact bytestring.
:param fields: The fields :param fields: The fields
:type fields: bytes :type fields: bytes
@@ -110,7 +110,7 @@ def pack_fields(*fields: bytes) -> bytes:
def unpack_fields(raw: bytes) -> Sequence[bytes]: def unpack_fields(raw: bytes) -> Sequence[bytes]:
''' '''
Unpacks the field from a bytestring. Unpacks the field from a bytestring.
:param raw: The packed fields :param raw: The packed fields
:type raw: bytes :type raw: bytes
+7 -7
View File
@@ -28,7 +28,7 @@ __all__ = [
def is_number(string: str, base: int = 10) -> bool: def is_number(string: str, base: int = 10) -> bool:
''' '''
Checks whether the string is a number. Checks whether the string is a number.
:param string: The string :param string: The string
:type string: str :type string: str
:param base: The base :param base: The base
@@ -48,7 +48,7 @@ def is_number(string: str, base: int = 10) -> bool:
def int_to_bytes(n: int, signed: bool = False) -> bytes: def int_to_bytes(n: int, signed: bool = False) -> bytes:
''' '''
Converts the integer into bytes (dynamic length). Converts the integer into bytes (dynamic length).
:param n: The integer :param n: The integer
:type n: int :type n: int
:param signed: Whether the integer is signed or not. :param signed: Whether the integer is signed or not.
@@ -76,7 +76,7 @@ def _bits_to_byte(bits: Tuple[Literal[0, 1], Literal[0, 1], Literal[0, 1], Liter
def bits_to_byte(*bits: Literal[0, 1]) -> bytes: def bits_to_byte(*bits: Literal[0, 1]) -> bytes:
''' '''
Converts the bits into a single byte. Converts the bits into a single byte.
:param bits: The bits :param bits: The bits
:type bits: int :type bits: int
@@ -94,7 +94,7 @@ def bits_to_byte(*bits: Literal[0, 1]) -> bytes:
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]]: 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. Converts the byte to a tuple of bits.
:param b: The byte :param b: The byte
:type b: bytes :type b: bytes
@@ -111,7 +111,7 @@ def byte_to_bits(b: bytes) -> Tuple[Literal[0, 1], Literal[0, 1], Literal[0, 1],
def get_next_lower_integer_multiple(value: int, multiple: int) -> int: def get_next_lower_integer_multiple(value: int, multiple: int) -> int:
''' '''
Returns the next lower integer multiple. Returns the next lower integer multiple.
:param value: The value :param value: The value
:type value: int :type value: int
:param multiple: The factor :param multiple: The factor
@@ -125,7 +125,7 @@ def get_next_lower_integer_multiple(value: int, multiple: int) -> int:
def find_nearest_lower_number(number_list: Sequence, target: int | float) -> int | float: def find_nearest_lower_number(number_list: Sequence, target: int | float) -> int | float:
''' '''
Finds the nearest lower number to the target from the list. Finds the nearest lower number to the target from the list.
:param number_list: The list :param number_list: The list
:type number_list: Sequence :type number_list: Sequence
:param target: The target :param target: The target
@@ -144,7 +144,7 @@ def find_nearest_lower_number(number_list: Sequence, target: int | float) -> int
def create_file_if_not_exists(path: str, content: bytes = b''): def create_file_if_not_exists(path: str, content: bytes = b''):
''' '''
Creates a file if it does not exist. Creates a file if it does not exist.
:param path: The file path :param path: The file path
:type path: str :type path: str
:param content: Optional, the content of the file if it is newly created. :param content: Optional, the content of the file if it is newly created.