Add symmetric encryption and tests
This commit is contained in:
@@ -6,6 +6,7 @@ description = "Collection of utils for Python scripts"
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"cryptography>=46.0.3",
|
||||||
"jmespath>=1.0.1",
|
"jmespath>=1.0.1",
|
||||||
"psutil>=7.0.0",
|
"psutil>=7.0.0",
|
||||||
"requests>=2.32.4",
|
"requests>=2.32.4",
|
||||||
|
|||||||
0
src/corelibs/encryption_handling/__init__.py
Normal file
0
src/corelibs/encryption_handling/__init__.py
Normal file
152
src/corelibs/encryption_handling/symmetric_encryption.py
Normal file
152
src/corelibs/encryption_handling/symmetric_encryption.py
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
"""
|
||||||
|
simple symmetric encryption
|
||||||
|
Will be moved to CoreLibs
|
||||||
|
TODO: set key per encryption run
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
from typing import TypedDict, cast
|
||||||
|
from cryptography.fernet import Fernet
|
||||||
|
from cryptography.hazmat.primitives import hashes
|
||||||
|
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||||
|
|
||||||
|
|
||||||
|
class PackageData(TypedDict):
|
||||||
|
"""encryption package"""
|
||||||
|
encrypted_data: str
|
||||||
|
salt: str
|
||||||
|
key_hash: str
|
||||||
|
|
||||||
|
|
||||||
|
class SymmetricEncryption:
|
||||||
|
"""
|
||||||
|
simple encryption
|
||||||
|
|
||||||
|
the encrypted package has "encrypted_data" and "salt" as fields, salt is needed to create the
|
||||||
|
key from the password to decrypt
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, password: str):
|
||||||
|
if not password:
|
||||||
|
raise ValueError("A password must be set")
|
||||||
|
self.password = password
|
||||||
|
self.password_hash = hashlib.sha256(password.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
def __derive_key_from_password(self, password: str, salt: bytes) -> bytes:
|
||||||
|
_password = password.encode('utf-8')
|
||||||
|
kdf = PBKDF2HMAC(
|
||||||
|
algorithm=hashes.SHA256(),
|
||||||
|
length=32,
|
||||||
|
salt=salt,
|
||||||
|
iterations=100000,
|
||||||
|
)
|
||||||
|
key = base64.urlsafe_b64encode(kdf.derive(_password))
|
||||||
|
return key
|
||||||
|
|
||||||
|
def __encrypt_with_metadata(self, data: str | bytes) -> PackageData:
|
||||||
|
"""Encrypt data and include salt if password-based"""
|
||||||
|
# convert to bytes (for encoding)
|
||||||
|
if isinstance(data, str):
|
||||||
|
data = data.encode('utf-8')
|
||||||
|
|
||||||
|
# generate salt and key from password
|
||||||
|
salt = os.urandom(16)
|
||||||
|
key = self.__derive_key_from_password(self.password, salt)
|
||||||
|
# init the cypher suit
|
||||||
|
cipher_suite = Fernet(key)
|
||||||
|
|
||||||
|
encrypted_data = cipher_suite.encrypt(data)
|
||||||
|
|
||||||
|
# If using password, include salt in the result
|
||||||
|
return {
|
||||||
|
'encrypted_data': base64.urlsafe_b64encode(encrypted_data).decode('utf-8'),
|
||||||
|
'salt': base64.urlsafe_b64encode(salt).decode('utf-8'),
|
||||||
|
'key_hash': hashlib.sha256(key).hexdigest()
|
||||||
|
}
|
||||||
|
|
||||||
|
def encrypt_with_metadata(self, data: str | bytes, return_as: str = 'str') -> str | bytes | PackageData:
|
||||||
|
"""encrypt with metadata, but returns data in string"""
|
||||||
|
match return_as:
|
||||||
|
case 'str':
|
||||||
|
return self.encrypt_with_metadata_return_str(data)
|
||||||
|
case 'json':
|
||||||
|
return self.encrypt_with_metadata_return_str(data)
|
||||||
|
case 'bytes':
|
||||||
|
return self.encrypt_with_metadata_return_bytes(data)
|
||||||
|
case 'dict':
|
||||||
|
return self.encrypt_with_metadata_return_dict(data)
|
||||||
|
case _:
|
||||||
|
# default is string json
|
||||||
|
return self.encrypt_with_metadata_return_str(data)
|
||||||
|
|
||||||
|
def encrypt_with_metadata_return_dict(self, data: str | bytes) -> PackageData:
|
||||||
|
"""encrypt with metadata, but returns data as PackageData dict"""
|
||||||
|
return self.__encrypt_with_metadata(data)
|
||||||
|
|
||||||
|
def encrypt_with_metadata_return_str(self, data: str | bytes) -> str:
|
||||||
|
"""encrypt with metadata, but returns data in string"""
|
||||||
|
return json.dumps(self.__encrypt_with_metadata(data))
|
||||||
|
|
||||||
|
def encrypt_with_metadata_return_bytes(self, data: str | bytes) -> bytes:
|
||||||
|
"""encrypt with metadata, but returns data in bytes"""
|
||||||
|
return json.dumps(self.__encrypt_with_metadata(data)).encode('utf-8')
|
||||||
|
|
||||||
|
def decrypt_with_metadata(self, encrypted_package: str | bytes | PackageData, password: str | None = None) -> str:
|
||||||
|
"""Decrypt data that may include metadata"""
|
||||||
|
try:
|
||||||
|
# Try to parse as JSON (password-based encryption)
|
||||||
|
if isinstance(encrypted_package, bytes):
|
||||||
|
package_data = cast(PackageData, json.loads(encrypted_package.decode('utf-8')))
|
||||||
|
elif isinstance(encrypted_package, str):
|
||||||
|
package_data = cast(PackageData, json.loads(str(encrypted_package)))
|
||||||
|
else:
|
||||||
|
package_data = encrypted_package
|
||||||
|
|
||||||
|
encrypted_data = base64.urlsafe_b64decode(package_data['encrypted_data'])
|
||||||
|
salt = base64.urlsafe_b64decode(package_data['salt'])
|
||||||
|
pwd = password or self.password
|
||||||
|
key = self.__derive_key_from_password(pwd, salt)
|
||||||
|
if package_data['key_hash'] != hashlib.sha256(key).hexdigest():
|
||||||
|
raise ValueError("Key hash is not matching, possible invalid password")
|
||||||
|
cipher_suite = Fernet(key)
|
||||||
|
decrypted_data = cipher_suite.decrypt(encrypted_data)
|
||||||
|
|
||||||
|
except (json.JSONDecodeError, KeyError, UnicodeDecodeError) as e:
|
||||||
|
raise ValueError(f"Invalid encrypted package format {e}") from e
|
||||||
|
|
||||||
|
return decrypted_data.decode('utf-8')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encrypt_data(data: str | bytes, password: str) -> str:
|
||||||
|
"""
|
||||||
|
Static method to encrypt some data
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
data {str | bytes} -- _description_
|
||||||
|
password {str} -- _description_
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str -- _description_
|
||||||
|
"""
|
||||||
|
encryptor = SymmetricEncryption(password)
|
||||||
|
return encryptor.encrypt_with_metadata_return_str(data)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def decrypt_data(data: str | bytes | PackageData, password: str) -> str:
|
||||||
|
"""
|
||||||
|
Static method to decrypt some data
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
data {str | bytes | PackageData} -- _description_
|
||||||
|
password {str} -- _description_
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str -- _description_
|
||||||
|
"""
|
||||||
|
decryptor = SymmetricEncryption(password)
|
||||||
|
return decryptor.decrypt_with_metadata(data, password=password)
|
||||||
|
|
||||||
|
# __END__
|
||||||
34
test-run/encryption/symmetric_encryption.py
Normal file
34
test-run/encryption/symmetric_encryption.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
Symmetric encryption test
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
from corelibs.debug_handling.dump_data import dump_data
|
||||||
|
from corelibs.encryption_handling.symmetric_encryption import SymmetricEncryption
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""
|
||||||
|
Comment
|
||||||
|
"""
|
||||||
|
password = "strongpassword"
|
||||||
|
se = SymmetricEncryption(password)
|
||||||
|
|
||||||
|
plaintext = "Hello, World!"
|
||||||
|
ciphertext = se.encrypt_with_metadata_return_str(plaintext)
|
||||||
|
decrypted = se.decrypt_with_metadata(ciphertext)
|
||||||
|
print(f"Encrypted: {dump_data(json.loads(ciphertext))}")
|
||||||
|
print(f"Input: {plaintext} -> {decrypted}")
|
||||||
|
|
||||||
|
static_ciphertext = SymmetricEncryption.encrypt_data(plaintext, password)
|
||||||
|
decrypted = SymmetricEncryption.decrypt_data(static_ciphertext, password)
|
||||||
|
print(f"Static Encrypted: {dump_data(json.loads(static_ciphertext))}")
|
||||||
|
print(f"Input: {plaintext} -> {decrypted}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
|
# __END__
|
||||||
3
tests/unit/encryption_handling/__init__.py
Normal file
3
tests/unit/encryption_handling/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
"""
|
||||||
|
Unit tests for encryption_handling module
|
||||||
|
"""
|
||||||
665
tests/unit/encryption_handling/test_symmetric_encryption.py
Normal file
665
tests/unit/encryption_handling/test_symmetric_encryption.py
Normal file
@@ -0,0 +1,665 @@
|
|||||||
|
"""
|
||||||
|
PyTest: encryption_handling/symmetric_encryption
|
||||||
|
"""
|
||||||
|
# pylint: disable=redefined-outer-name
|
||||||
|
# ^ Disabled because pytest fixtures intentionally redefine names
|
||||||
|
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import pytest
|
||||||
|
from corelibs.encryption_handling.symmetric_encryption import (
|
||||||
|
SymmetricEncryption
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestSymmetricEncryptionInitialization:
|
||||||
|
"""Tests for SymmetricEncryption initialization"""
|
||||||
|
|
||||||
|
def test_valid_password_initialization(self):
|
||||||
|
"""Test initialization with a valid password"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
assert encryptor.password == "test_password"
|
||||||
|
assert encryptor.password_hash == hashlib.sha256("test_password".encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
def test_empty_password_raises_error(self):
|
||||||
|
"""Test that empty password raises ValueError"""
|
||||||
|
with pytest.raises(ValueError, match="A password must be set"):
|
||||||
|
SymmetricEncryption("")
|
||||||
|
|
||||||
|
def test_password_hash_is_consistent(self):
|
||||||
|
"""Test that password hash is consistently generated"""
|
||||||
|
encryptor1 = SymmetricEncryption("test_password")
|
||||||
|
encryptor2 = SymmetricEncryption("test_password")
|
||||||
|
assert encryptor1.password_hash == encryptor2.password_hash
|
||||||
|
|
||||||
|
def test_different_passwords_different_hashes(self):
|
||||||
|
"""Test that different passwords produce different hashes"""
|
||||||
|
encryptor1 = SymmetricEncryption("password1")
|
||||||
|
encryptor2 = SymmetricEncryption("password2")
|
||||||
|
assert encryptor1.password_hash != encryptor2.password_hash
|
||||||
|
|
||||||
|
|
||||||
|
class TestEncryptWithMetadataReturnDict:
|
||||||
|
"""Tests for encrypt_with_metadata_return_dict method"""
|
||||||
|
|
||||||
|
def test_encrypt_string_returns_package_data(self):
|
||||||
|
"""Test encrypting a string returns PackageData dict"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
result = encryptor.encrypt_with_metadata_return_dict("test data")
|
||||||
|
|
||||||
|
assert isinstance(result, dict)
|
||||||
|
assert 'encrypted_data' in result
|
||||||
|
assert 'salt' in result
|
||||||
|
assert 'key_hash' in result
|
||||||
|
|
||||||
|
def test_encrypt_bytes_returns_package_data(self):
|
||||||
|
"""Test encrypting bytes returns PackageData dict"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
result = encryptor.encrypt_with_metadata_return_dict(b"test data")
|
||||||
|
|
||||||
|
assert isinstance(result, dict)
|
||||||
|
assert 'encrypted_data' in result
|
||||||
|
assert 'salt' in result
|
||||||
|
assert 'key_hash' in result
|
||||||
|
|
||||||
|
def test_encrypted_data_is_base64_encoded(self):
|
||||||
|
"""Test that encrypted_data is base64 encoded"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
result = encryptor.encrypt_with_metadata_return_dict("test data")
|
||||||
|
|
||||||
|
# Should not raise exception when decoding
|
||||||
|
base64.urlsafe_b64decode(result['encrypted_data'])
|
||||||
|
|
||||||
|
def test_salt_is_base64_encoded(self):
|
||||||
|
"""Test that salt is base64 encoded"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
result = encryptor.encrypt_with_metadata_return_dict("test data")
|
||||||
|
|
||||||
|
# Should not raise exception when decoding
|
||||||
|
salt = base64.urlsafe_b64decode(result['salt'])
|
||||||
|
# Salt should be 16 bytes
|
||||||
|
assert len(salt) == 16
|
||||||
|
|
||||||
|
def test_key_hash_is_valid_hex(self):
|
||||||
|
"""Test that key_hash is a valid hex string"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
result = encryptor.encrypt_with_metadata_return_dict("test data")
|
||||||
|
|
||||||
|
# Should be 64 characters (SHA256 hex)
|
||||||
|
assert len(result['key_hash']) == 64
|
||||||
|
# Should only contain hex characters
|
||||||
|
int(result['key_hash'], 16)
|
||||||
|
|
||||||
|
def test_different_salts_for_each_encryption(self):
|
||||||
|
"""Test that each encryption uses a different salt"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
result1 = encryptor.encrypt_with_metadata_return_dict("test data")
|
||||||
|
result2 = encryptor.encrypt_with_metadata_return_dict("test data")
|
||||||
|
|
||||||
|
assert result1['salt'] != result2['salt']
|
||||||
|
assert result1['encrypted_data'] != result2['encrypted_data']
|
||||||
|
|
||||||
|
|
||||||
|
class TestEncryptWithMetadataReturnStr:
|
||||||
|
"""Tests for encrypt_with_metadata_return_str method"""
|
||||||
|
|
||||||
|
def test_returns_json_string(self):
|
||||||
|
"""Test that method returns a valid JSON string"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
result = encryptor.encrypt_with_metadata_return_str("test data")
|
||||||
|
|
||||||
|
assert isinstance(result, str)
|
||||||
|
# Should be valid JSON
|
||||||
|
parsed = json.loads(result)
|
||||||
|
assert 'encrypted_data' in parsed
|
||||||
|
assert 'salt' in parsed
|
||||||
|
assert 'key_hash' in parsed
|
||||||
|
|
||||||
|
def test_json_string_parseable(self):
|
||||||
|
"""Test that returned JSON string can be parsed back"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
result = encryptor.encrypt_with_metadata_return_str("test data")
|
||||||
|
|
||||||
|
parsed = json.loads(result)
|
||||||
|
assert isinstance(parsed, dict)
|
||||||
|
|
||||||
|
|
||||||
|
class TestEncryptWithMetadataReturnBytes:
|
||||||
|
"""Tests for encrypt_with_metadata_return_bytes method"""
|
||||||
|
|
||||||
|
def test_returns_bytes(self):
|
||||||
|
"""Test that method returns bytes"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
result = encryptor.encrypt_with_metadata_return_bytes("test data")
|
||||||
|
|
||||||
|
assert isinstance(result, bytes)
|
||||||
|
|
||||||
|
def test_bytes_contains_valid_json(self):
|
||||||
|
"""Test that returned bytes contain valid JSON"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
result = encryptor.encrypt_with_metadata_return_bytes("test data")
|
||||||
|
|
||||||
|
# Should be valid JSON when decoded
|
||||||
|
parsed = json.loads(result.decode('utf-8'))
|
||||||
|
assert 'encrypted_data' in parsed
|
||||||
|
assert 'salt' in parsed
|
||||||
|
assert 'key_hash' in parsed
|
||||||
|
|
||||||
|
|
||||||
|
class TestEncryptWithMetadata:
|
||||||
|
"""Tests for encrypt_with_metadata method with different return types"""
|
||||||
|
|
||||||
|
def test_return_as_str(self):
|
||||||
|
"""Test encrypt_with_metadata with return_as='str'"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
result = encryptor.encrypt_with_metadata("test data", return_as='str')
|
||||||
|
|
||||||
|
assert isinstance(result, str)
|
||||||
|
json.loads(result) # Should be valid JSON
|
||||||
|
|
||||||
|
def test_return_as_json(self):
|
||||||
|
"""Test encrypt_with_metadata with return_as='json'"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
result = encryptor.encrypt_with_metadata("test data", return_as='json')
|
||||||
|
|
||||||
|
assert isinstance(result, str)
|
||||||
|
json.loads(result) # Should be valid JSON
|
||||||
|
|
||||||
|
def test_return_as_bytes(self):
|
||||||
|
"""Test encrypt_with_metadata with return_as='bytes'"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
result = encryptor.encrypt_with_metadata("test data", return_as='bytes')
|
||||||
|
|
||||||
|
assert isinstance(result, bytes)
|
||||||
|
|
||||||
|
def test_return_as_dict(self):
|
||||||
|
"""Test encrypt_with_metadata with return_as='dict'"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
result = encryptor.encrypt_with_metadata("test data", return_as='dict')
|
||||||
|
|
||||||
|
assert isinstance(result, dict)
|
||||||
|
assert 'encrypted_data' in result
|
||||||
|
|
||||||
|
def test_default_return_type(self):
|
||||||
|
"""Test encrypt_with_metadata default return type"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
result = encryptor.encrypt_with_metadata("test data")
|
||||||
|
|
||||||
|
# Default should be 'str'
|
||||||
|
assert isinstance(result, str)
|
||||||
|
|
||||||
|
def test_invalid_return_type_defaults_to_str(self):
|
||||||
|
"""Test that invalid return_as defaults to str"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
result = encryptor.encrypt_with_metadata("test data", return_as='invalid')
|
||||||
|
|
||||||
|
assert isinstance(result, str)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDecryptWithMetadata:
|
||||||
|
"""Tests for decrypt_with_metadata method"""
|
||||||
|
|
||||||
|
def test_decrypt_string_package(self):
|
||||||
|
"""Test decrypting a string JSON package"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_str("test data")
|
||||||
|
decrypted = encryptor.decrypt_with_metadata(encrypted)
|
||||||
|
|
||||||
|
assert decrypted == "test data"
|
||||||
|
|
||||||
|
def test_decrypt_bytes_package(self):
|
||||||
|
"""Test decrypting a bytes JSON package"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_bytes("test data")
|
||||||
|
decrypted = encryptor.decrypt_with_metadata(encrypted)
|
||||||
|
|
||||||
|
assert decrypted == "test data"
|
||||||
|
|
||||||
|
def test_decrypt_dict_package(self):
|
||||||
|
"""Test decrypting a dict PackageData"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_dict("test data")
|
||||||
|
decrypted = encryptor.decrypt_with_metadata(encrypted)
|
||||||
|
|
||||||
|
assert decrypted == "test data"
|
||||||
|
|
||||||
|
def test_decrypt_with_different_password_fails(self):
|
||||||
|
"""Test that decrypting with wrong password fails"""
|
||||||
|
encryptor = SymmetricEncryption("password1")
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_str("test data")
|
||||||
|
|
||||||
|
decryptor = SymmetricEncryption("password2")
|
||||||
|
with pytest.raises(ValueError, match="Key hash is not matching"):
|
||||||
|
decryptor.decrypt_with_metadata(encrypted)
|
||||||
|
|
||||||
|
def test_decrypt_with_explicit_password(self):
|
||||||
|
"""Test decrypting with explicitly provided password"""
|
||||||
|
encryptor = SymmetricEncryption("password1")
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_str("test data")
|
||||||
|
|
||||||
|
# Decrypt with different password parameter
|
||||||
|
decryptor = SymmetricEncryption("password1")
|
||||||
|
decrypted = decryptor.decrypt_with_metadata(encrypted, password="password1")
|
||||||
|
|
||||||
|
assert decrypted == "test data"
|
||||||
|
|
||||||
|
def test_decrypt_invalid_json_raises_error(self):
|
||||||
|
"""Test that invalid JSON raises ValueError"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="Invalid encrypted package format"):
|
||||||
|
encryptor.decrypt_with_metadata("not valid json")
|
||||||
|
|
||||||
|
def test_decrypt_missing_fields_raises_error(self):
|
||||||
|
"""Test that missing required fields raises ValueError"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
invalid_package = json.dumps({"encrypted_data": "test"})
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="Invalid encrypted package format"):
|
||||||
|
encryptor.decrypt_with_metadata(invalid_package)
|
||||||
|
|
||||||
|
def test_decrypt_unicode_data(self):
|
||||||
|
"""Test encrypting and decrypting unicode data"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
unicode_data = "Hello 世界 🌍"
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_str(unicode_data)
|
||||||
|
decrypted = encryptor.decrypt_with_metadata(encrypted)
|
||||||
|
|
||||||
|
assert decrypted == unicode_data
|
||||||
|
|
||||||
|
def test_decrypt_empty_string(self):
|
||||||
|
"""Test encrypting and decrypting empty string"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_str("")
|
||||||
|
decrypted = encryptor.decrypt_with_metadata(encrypted)
|
||||||
|
|
||||||
|
assert decrypted == ""
|
||||||
|
|
||||||
|
def test_decrypt_long_data(self):
|
||||||
|
"""Test encrypting and decrypting long data"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
long_data = "A" * 10000
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_str(long_data)
|
||||||
|
decrypted = encryptor.decrypt_with_metadata(encrypted)
|
||||||
|
|
||||||
|
assert decrypted == long_data
|
||||||
|
|
||||||
|
|
||||||
|
class TestStaticMethods:
|
||||||
|
"""Tests for static methods encrypt_data and decrypt_data"""
|
||||||
|
|
||||||
|
def test_encrypt_data_static_method(self):
|
||||||
|
"""Test static encrypt_data method"""
|
||||||
|
encrypted = SymmetricEncryption.encrypt_data("test data", "test_password")
|
||||||
|
|
||||||
|
assert isinstance(encrypted, str)
|
||||||
|
# Should be valid JSON
|
||||||
|
parsed = json.loads(encrypted)
|
||||||
|
assert 'encrypted_data' in parsed
|
||||||
|
assert 'salt' in parsed
|
||||||
|
assert 'key_hash' in parsed
|
||||||
|
|
||||||
|
def test_decrypt_data_static_method(self):
|
||||||
|
"""Test static decrypt_data method"""
|
||||||
|
encrypted = SymmetricEncryption.encrypt_data("test data", "test_password")
|
||||||
|
decrypted = SymmetricEncryption.decrypt_data(encrypted, "test_password")
|
||||||
|
|
||||||
|
assert decrypted == "test data"
|
||||||
|
|
||||||
|
def test_static_methods_roundtrip(self):
|
||||||
|
"""Test complete roundtrip using static methods"""
|
||||||
|
original = "test data with special chars: !@#$%^&*()"
|
||||||
|
encrypted = SymmetricEncryption.encrypt_data(original, "test_password")
|
||||||
|
decrypted = SymmetricEncryption.decrypt_data(encrypted, "test_password")
|
||||||
|
|
||||||
|
assert decrypted == original
|
||||||
|
|
||||||
|
def test_static_decrypt_with_bytes(self):
|
||||||
|
"""Test static decrypt_data with bytes input"""
|
||||||
|
encrypted = SymmetricEncryption.encrypt_data("test data", "test_password")
|
||||||
|
encrypted_bytes = encrypted.encode('utf-8')
|
||||||
|
decrypted = SymmetricEncryption.decrypt_data(encrypted_bytes, "test_password")
|
||||||
|
|
||||||
|
assert decrypted == "test data"
|
||||||
|
|
||||||
|
def test_static_decrypt_with_dict(self):
|
||||||
|
"""Test static decrypt_data with PackageData dict"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
encrypted_dict = encryptor.encrypt_with_metadata_return_dict("test data")
|
||||||
|
decrypted = SymmetricEncryption.decrypt_data(encrypted_dict, "test_password")
|
||||||
|
|
||||||
|
assert decrypted == "test data"
|
||||||
|
|
||||||
|
def test_static_encrypt_bytes_data(self):
|
||||||
|
"""Test static encrypt_data with bytes input"""
|
||||||
|
encrypted = SymmetricEncryption.encrypt_data(b"test data", "test_password")
|
||||||
|
decrypted = SymmetricEncryption.decrypt_data(encrypted, "test_password")
|
||||||
|
|
||||||
|
assert decrypted == "test data"
|
||||||
|
|
||||||
|
|
||||||
|
class TestEncryptionSecurity:
|
||||||
|
"""Security-related tests for encryption"""
|
||||||
|
|
||||||
|
def test_same_data_different_encryption(self):
|
||||||
|
"""Test that same data produces different encrypted outputs due to salt"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
encrypted1 = encryptor.encrypt_with_metadata_return_str("test data")
|
||||||
|
encrypted2 = encryptor.encrypt_with_metadata_return_str("test data")
|
||||||
|
|
||||||
|
assert encrypted1 != encrypted2
|
||||||
|
|
||||||
|
def test_password_not_recoverable_from_hash(self):
|
||||||
|
"""Test that password hash is one-way"""
|
||||||
|
encryptor = SymmetricEncryption("secret_password")
|
||||||
|
# The password_hash should be SHA256 hex (64 chars)
|
||||||
|
assert len(encryptor.password_hash) == 64
|
||||||
|
# Password should not be easily derivable from hash
|
||||||
|
assert "secret_password" not in encryptor.password_hash
|
||||||
|
|
||||||
|
def test_encrypted_data_not_plaintext(self):
|
||||||
|
"""Test that encrypted data doesn't contain plaintext"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
plaintext = "very_secret_data_12345"
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_str(plaintext)
|
||||||
|
|
||||||
|
# Plaintext should not appear in encrypted output
|
||||||
|
assert plaintext not in encrypted
|
||||||
|
|
||||||
|
def test_modified_encrypted_data_fails_decryption(self):
|
||||||
|
"""Test that modified encrypted data fails to decrypt"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_str("test data")
|
||||||
|
|
||||||
|
# Modify the encrypted data
|
||||||
|
encrypted_dict = json.loads(encrypted)
|
||||||
|
encrypted_dict['encrypted_data'] = encrypted_dict['encrypted_data'][:-5] + "AAAAA"
|
||||||
|
modified_encrypted = json.dumps(encrypted_dict)
|
||||||
|
|
||||||
|
# Should fail to decrypt
|
||||||
|
with pytest.raises(Exception): # Fernet will raise an exception
|
||||||
|
encryptor.decrypt_with_metadata(modified_encrypted)
|
||||||
|
|
||||||
|
def test_modified_salt_fails_decryption(self):
|
||||||
|
"""Test that modified salt fails to decrypt"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_str("test data")
|
||||||
|
|
||||||
|
# Modify the salt
|
||||||
|
encrypted_dict = json.loads(encrypted)
|
||||||
|
original_salt = base64.urlsafe_b64decode(encrypted_dict['salt'])
|
||||||
|
modified_salt = bytes([b ^ 1 for b in original_salt])
|
||||||
|
encrypted_dict['salt'] = base64.urlsafe_b64encode(modified_salt).decode('utf-8')
|
||||||
|
modified_encrypted = json.dumps(encrypted_dict)
|
||||||
|
|
||||||
|
# Should fail to decrypt due to key hash mismatch
|
||||||
|
with pytest.raises(ValueError, match="Key hash is not matching"):
|
||||||
|
encryptor.decrypt_with_metadata(modified_encrypted)
|
||||||
|
|
||||||
|
|
||||||
|
class TestEdgeCases:
|
||||||
|
"""Edge case tests"""
|
||||||
|
|
||||||
|
def test_very_long_password(self):
|
||||||
|
"""Test with very long password"""
|
||||||
|
long_password = "a" * 1000
|
||||||
|
encryptor = SymmetricEncryption(long_password)
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_str("test data")
|
||||||
|
decrypted = encryptor.decrypt_with_metadata(encrypted)
|
||||||
|
|
||||||
|
assert decrypted == "test data"
|
||||||
|
|
||||||
|
def test_special_characters_in_data(self):
|
||||||
|
"""Test encryption of data with special characters"""
|
||||||
|
special_data = "!@#$%^&*()_+-=[]{}|;':\",./<>?\n\t\r"
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_str(special_data)
|
||||||
|
decrypted = encryptor.decrypt_with_metadata(encrypted)
|
||||||
|
|
||||||
|
assert decrypted == special_data
|
||||||
|
|
||||||
|
def test_binary_data_utf8_bytes(self):
|
||||||
|
"""Test encryption of UTF-8 encoded bytes"""
|
||||||
|
# Test with UTF-8 encoded bytes
|
||||||
|
utf8_bytes = "Hello 世界 🌍".encode('utf-8')
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_str(utf8_bytes)
|
||||||
|
decrypted = encryptor.decrypt_with_metadata(encrypted)
|
||||||
|
|
||||||
|
assert decrypted == "Hello 世界 🌍"
|
||||||
|
|
||||||
|
def test_binary_data_with_base64_encoding(self):
|
||||||
|
"""Test encryption of arbitrary binary data using base64 encoding"""
|
||||||
|
# For arbitrary binary data, encode to base64 first
|
||||||
|
binary_data = bytes(range(256))
|
||||||
|
base64_encoded = base64.b64encode(binary_data).decode('utf-8')
|
||||||
|
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_str(base64_encoded)
|
||||||
|
decrypted = encryptor.decrypt_with_metadata(encrypted)
|
||||||
|
|
||||||
|
# Decode back to binary
|
||||||
|
decoded_binary = base64.b64decode(decrypted)
|
||||||
|
assert decoded_binary == binary_data
|
||||||
|
|
||||||
|
def test_binary_data_image_simulation(self):
|
||||||
|
"""Test encryption of simulated binary image data"""
|
||||||
|
# Simulate image binary data (random bytes)
|
||||||
|
image_data = os.urandom(1024) # 1KB of random binary data
|
||||||
|
base64_encoded = base64.b64encode(image_data).decode('utf-8')
|
||||||
|
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_str(base64_encoded)
|
||||||
|
decrypted = encryptor.decrypt_with_metadata(encrypted)
|
||||||
|
|
||||||
|
# Verify round-trip
|
||||||
|
decoded_data = base64.b64decode(decrypted)
|
||||||
|
assert decoded_data == image_data
|
||||||
|
|
||||||
|
def test_binary_data_with_null_bytes(self):
|
||||||
|
"""Test encryption of data containing null bytes"""
|
||||||
|
# Create data with null bytes
|
||||||
|
data_with_nulls = "text\x00with\x00nulls\x00bytes"
|
||||||
|
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_str(data_with_nulls)
|
||||||
|
decrypted = encryptor.decrypt_with_metadata(encrypted)
|
||||||
|
|
||||||
|
assert decrypted == data_with_nulls
|
||||||
|
|
||||||
|
def test_binary_data_bytes_input(self):
|
||||||
|
"""Test encryption with bytes input directly"""
|
||||||
|
# UTF-8 compatible bytes
|
||||||
|
byte_data = b"Binary data test"
|
||||||
|
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_str(byte_data)
|
||||||
|
decrypted = encryptor.decrypt_with_metadata(encrypted)
|
||||||
|
|
||||||
|
assert decrypted == "Binary data test"
|
||||||
|
|
||||||
|
def test_binary_data_large_file_simulation(self):
|
||||||
|
"""Test encryption of large binary data (simulated file)"""
|
||||||
|
# Simulate a larger binary file (10KB)
|
||||||
|
large_data = os.urandom(10240)
|
||||||
|
base64_encoded = base64.b64encode(large_data).decode('utf-8')
|
||||||
|
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_str(base64_encoded)
|
||||||
|
decrypted = encryptor.decrypt_with_metadata(encrypted)
|
||||||
|
|
||||||
|
# Verify integrity
|
||||||
|
decoded_data = base64.b64decode(decrypted)
|
||||||
|
assert len(decoded_data) == 10240
|
||||||
|
assert decoded_data == large_data
|
||||||
|
|
||||||
|
def test_binary_data_json_with_base64(self):
|
||||||
|
"""Test encryption of JSON containing base64 encoded binary data"""
|
||||||
|
binary_data = os.urandom(256)
|
||||||
|
json_data = json.dumps({
|
||||||
|
"filename": "test.bin",
|
||||||
|
"data": base64.b64encode(binary_data).decode('utf-8'),
|
||||||
|
"size": len(binary_data)
|
||||||
|
})
|
||||||
|
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_str(json_data)
|
||||||
|
decrypted = encryptor.decrypt_with_metadata(encrypted)
|
||||||
|
|
||||||
|
# Parse and verify
|
||||||
|
parsed = json.loads(decrypted)
|
||||||
|
assert parsed["filename"] == "test.bin"
|
||||||
|
assert parsed["size"] == 256
|
||||||
|
decoded_binary = base64.b64decode(parsed["data"])
|
||||||
|
assert decoded_binary == binary_data
|
||||||
|
|
||||||
|
def test_numeric_password(self):
|
||||||
|
"""Test with numeric string password"""
|
||||||
|
encryptor = SymmetricEncryption("12345")
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_str("test data")
|
||||||
|
decrypted = encryptor.decrypt_with_metadata(encrypted)
|
||||||
|
|
||||||
|
assert decrypted == "test data"
|
||||||
|
|
||||||
|
def test_unicode_password(self):
|
||||||
|
"""Test with unicode password"""
|
||||||
|
encryptor = SymmetricEncryption("パスワード123")
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_str("test data")
|
||||||
|
decrypted = encryptor.decrypt_with_metadata(encrypted)
|
||||||
|
|
||||||
|
assert decrypted == "test data"
|
||||||
|
|
||||||
|
|
||||||
|
class TestIntegration:
|
||||||
|
"""Integration tests"""
|
||||||
|
|
||||||
|
def test_multiple_encrypt_decrypt_cycles(self):
|
||||||
|
"""Test multiple encryption/decryption cycles"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
original = "test data"
|
||||||
|
|
||||||
|
# Encrypt and decrypt multiple times
|
||||||
|
for _ in range(5):
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_str(original)
|
||||||
|
decrypted = encryptor.decrypt_with_metadata(encrypted)
|
||||||
|
assert decrypted == original
|
||||||
|
|
||||||
|
def test_different_return_types_interoperability(self):
|
||||||
|
"""Test that different return types can be decrypted"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
original = "test data"
|
||||||
|
|
||||||
|
# Encrypt with different return types
|
||||||
|
encrypted_str = encryptor.encrypt_with_metadata_return_str(original)
|
||||||
|
encrypted_bytes = encryptor.encrypt_with_metadata_return_bytes(original)
|
||||||
|
encrypted_dict = encryptor.encrypt_with_metadata_return_dict(original)
|
||||||
|
|
||||||
|
# All should decrypt to the same value
|
||||||
|
assert encryptor.decrypt_with_metadata(encrypted_str) == original
|
||||||
|
assert encryptor.decrypt_with_metadata(encrypted_bytes) == original
|
||||||
|
assert encryptor.decrypt_with_metadata(encrypted_dict) == original
|
||||||
|
|
||||||
|
def test_cross_instance_encryption_decryption(self):
|
||||||
|
"""Test that different instances with same password can decrypt"""
|
||||||
|
encryptor1 = SymmetricEncryption("test_password")
|
||||||
|
encryptor2 = SymmetricEncryption("test_password")
|
||||||
|
|
||||||
|
encrypted = encryptor1.encrypt_with_metadata_return_str("test data")
|
||||||
|
decrypted = encryptor2.decrypt_with_metadata(encrypted)
|
||||||
|
|
||||||
|
assert decrypted == "test data"
|
||||||
|
|
||||||
|
def test_static_and_instance_methods_compatible(self):
|
||||||
|
"""Test that static and instance methods are compatible"""
|
||||||
|
# Encrypt with static method
|
||||||
|
encrypted = SymmetricEncryption.encrypt_data("test data", "test_password")
|
||||||
|
|
||||||
|
# Decrypt with instance method
|
||||||
|
decryptor = SymmetricEncryption("test_password")
|
||||||
|
decrypted = decryptor.decrypt_with_metadata(encrypted)
|
||||||
|
|
||||||
|
assert decrypted == "test data"
|
||||||
|
|
||||||
|
# And vice versa
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
encrypted2 = encryptor.encrypt_with_metadata_return_str("test data 2")
|
||||||
|
decrypted2 = SymmetricEncryption.decrypt_data(encrypted2, "test_password")
|
||||||
|
|
||||||
|
assert decrypted2 == "test data 2"
|
||||||
|
|
||||||
|
|
||||||
|
# Parametrized tests
|
||||||
|
@pytest.mark.parametrize("data", [
|
||||||
|
"simple text",
|
||||||
|
"text with spaces and punctuation!",
|
||||||
|
"123456789",
|
||||||
|
"unicode: こんにちは",
|
||||||
|
"emoji: 🔐🔑",
|
||||||
|
"",
|
||||||
|
"a" * 1000, # Long string
|
||||||
|
])
|
||||||
|
def test_encrypt_decrypt_various_data(data: str):
|
||||||
|
"""Parametrized test for various data types"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_str(data)
|
||||||
|
decrypted = encryptor.decrypt_with_metadata(encrypted)
|
||||||
|
assert decrypted == data
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("password", [
|
||||||
|
"simple",
|
||||||
|
"with spaces",
|
||||||
|
"special!@#$%",
|
||||||
|
"unicode世界",
|
||||||
|
"123456",
|
||||||
|
"a" * 100, # Long password
|
||||||
|
])
|
||||||
|
def test_various_passwords(password: str):
|
||||||
|
"""Parametrized test for various passwords"""
|
||||||
|
encryptor = SymmetricEncryption(password)
|
||||||
|
encrypted = encryptor.encrypt_with_metadata_return_str("test data")
|
||||||
|
decrypted = encryptor.decrypt_with_metadata(encrypted)
|
||||||
|
assert decrypted == "test data"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("return_type,expected_type", [
|
||||||
|
("str", str),
|
||||||
|
("json", str),
|
||||||
|
("bytes", bytes),
|
||||||
|
("dict", dict),
|
||||||
|
])
|
||||||
|
def test_return_types_parametrized(return_type: str, expected_type: type):
|
||||||
|
"""Parametrized test for different return types"""
|
||||||
|
encryptor = SymmetricEncryption("test_password")
|
||||||
|
result = encryptor.encrypt_with_metadata("test data", return_as=return_type)
|
||||||
|
assert isinstance(result, expected_type)
|
||||||
|
|
||||||
|
|
||||||
|
# Fixtures
|
||||||
|
@pytest.fixture
|
||||||
|
def encryptor() -> SymmetricEncryption:
|
||||||
|
"""Fixture providing a basic encryptor instance"""
|
||||||
|
return SymmetricEncryption("test_password")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sample_encrypted_data(encryptor: SymmetricEncryption) -> str:
|
||||||
|
"""Fixture providing sample encrypted data"""
|
||||||
|
return encryptor.encrypt_with_metadata_return_str("sample data")
|
||||||
|
|
||||||
|
|
||||||
|
def test_with_encryptor_fixture(encryptor: SymmetricEncryption) -> None:
|
||||||
|
"""Test using encryptor fixture"""
|
||||||
|
encrypted: str = encryptor.encrypt_with_metadata_return_str("test")
|
||||||
|
decrypted: str = encryptor.decrypt_with_metadata(encrypted)
|
||||||
|
assert decrypted == "test"
|
||||||
|
|
||||||
|
|
||||||
|
def test_with_encrypted_data_fixture(encryptor: SymmetricEncryption, sample_encrypted_data: str) -> None:
|
||||||
|
"""Test using encrypted data fixture"""
|
||||||
|
decrypted: str = encryptor.decrypt_with_metadata(sample_encrypted_data)
|
||||||
|
assert decrypted == "sample data"
|
||||||
|
|
||||||
|
# __END__
|
||||||
114
uv.lock
generated
114
uv.lock
generated
@@ -11,6 +11,51 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" },
|
{ url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cffi"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "charset-normalizer"
|
name = "charset-normalizer"
|
||||||
version = "3.4.3"
|
version = "3.4.3"
|
||||||
@@ -53,9 +98,10 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "corelibs"
|
name = "corelibs"
|
||||||
version = "0.25.0"
|
version = "0.25.1"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
{ name = "cryptography" },
|
||||||
{ name = "jmespath" },
|
{ name = "jmespath" },
|
||||||
{ name = "psutil" },
|
{ name = "psutil" },
|
||||||
{ name = "requests" },
|
{ name = "requests" },
|
||||||
@@ -69,6 +115,7 @@ dev = [
|
|||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
|
{ name = "cryptography", specifier = ">=46.0.3" },
|
||||||
{ name = "jmespath", specifier = ">=1.0.1" },
|
{ name = "jmespath", specifier = ">=1.0.1" },
|
||||||
{ name = "psutil", specifier = ">=7.0.0" },
|
{ name = "psutil", specifier = ">=7.0.0" },
|
||||||
{ name = "requests", specifier = ">=2.32.4" },
|
{ name = "requests", specifier = ">=2.32.4" },
|
||||||
@@ -133,6 +180,62 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/08/b6/fff6609354deba9aeec466e4bcaeb9d1ed3e5d60b14b57df2a36fb2273f2/coverage-7.10.5-py3-none-any.whl", hash = "sha256:0be24d35e4db1d23d0db5c0f6a74a962e2ec83c426b5cac09f4234aadef38e4a", size = 208736, upload-time = "2025-08-23T14:42:43.145Z" },
|
{ url = "https://files.pythonhosted.org/packages/08/b6/fff6609354deba9aeec466e4bcaeb9d1ed3e5d60b14b57df2a36fb2273f2/coverage-7.10.5-py3-none-any.whl", hash = "sha256:0be24d35e4db1d23d0db5c0f6a74a962e2ec83c426b5cac09f4234aadef38e4a", size = 208736, upload-time = "2025-08-23T14:42:43.145Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cryptography"
|
||||||
|
version = "46.0.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "3.10"
|
version = "3.10"
|
||||||
@@ -193,6 +296,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" },
|
{ url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pycparser"
|
||||||
|
version = "2.23"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pygments"
|
name = "pygments"
|
||||||
version = "2.19.2"
|
version = "2.19.2"
|
||||||
|
|||||||
Reference in New Issue
Block a user