winiutils 2.3.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- winiutils/__init__.py +1 -0
- winiutils/dev/__init__.py +1 -0
- winiutils/dev/builders/__init__.py +1 -0
- winiutils/dev/cli/__init__.py +1 -0
- winiutils/dev/cli/subcommands.py +6 -0
- winiutils/dev/configs/__init__.py +1 -0
- winiutils/dev/tests/__init__.py +1 -0
- winiutils/dev/tests/fixtures/__init__.py +1 -0
- winiutils/dev/tests/fixtures/fixtures.py +32 -0
- winiutils/main.py +9 -0
- winiutils/py.typed +0 -0
- winiutils/resources/__init__.py +1 -0
- winiutils/src/__init__.py +4 -0
- winiutils/src/data/__init__.py +8 -0
- winiutils/src/data/dataframe/__init__.py +7 -0
- winiutils/src/data/dataframe/cleaning.py +734 -0
- winiutils/src/data/structures/__init__.py +8 -0
- winiutils/src/data/structures/dicts.py +40 -0
- winiutils/src/data/structures/text/__init__.py +7 -0
- winiutils/src/data/structures/text/string.py +157 -0
- winiutils/src/iterating/__init__.py +8 -0
- winiutils/src/iterating/concurrent/__init__.py +9 -0
- winiutils/src/iterating/concurrent/concurrent.py +301 -0
- winiutils/src/iterating/concurrent/multiprocessing.py +186 -0
- winiutils/src/iterating/concurrent/multithreading.py +132 -0
- winiutils/src/iterating/iterate.py +45 -0
- winiutils/src/oop/__init__.py +7 -0
- winiutils/src/oop/mixins/__init__.py +8 -0
- winiutils/src/oop/mixins/meta.py +217 -0
- winiutils/src/oop/mixins/mixin.py +58 -0
- winiutils/src/security/__init__.py +8 -0
- winiutils/src/security/cryptography.py +100 -0
- winiutils/src/security/keyring.py +167 -0
- winiutils-2.3.12.dist-info/METADATA +283 -0
- winiutils-2.3.12.dist-info/RECORD +38 -0
- winiutils-2.3.12.dist-info/WHEEL +4 -0
- winiutils-2.3.12.dist-info/entry_points.txt +4 -0
- winiutils-2.3.12.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Cryptography utilities for secure data handling.
|
|
2
|
+
|
|
3
|
+
This module provides utility functions for working with cryptography,
|
|
4
|
+
including encryption and decryption using AES-GCM (Galois/Counter Mode).
|
|
5
|
+
AES-GCM provides both confidentiality and authenticity guarantees.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
9
|
+
>>> from winiutils.src.security.cryptography import (
|
|
10
|
+
... encrypt_with_aes_gcm,
|
|
11
|
+
... decrypt_with_aes_gcm,
|
|
12
|
+
... )
|
|
13
|
+
>>> key = AESGCM.generate_key(bit_length=256)
|
|
14
|
+
>>> aes_gcm = AESGCM(key)
|
|
15
|
+
>>> encrypted = encrypt_with_aes_gcm(aes_gcm, b"secret message")
|
|
16
|
+
>>> decrypted = decrypt_with_aes_gcm(aes_gcm, encrypted)
|
|
17
|
+
>>> decrypted
|
|
18
|
+
b'secret message'
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import os
|
|
22
|
+
|
|
23
|
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
24
|
+
|
|
25
|
+
IV_LEN = 12
|
|
26
|
+
"""int: Length of the initialization vector (IV) in bytes.
|
|
27
|
+
|
|
28
|
+
AES-GCM requires a 12-byte (96-bit) IV for optimal performance and security.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def encrypt_with_aes_gcm(
|
|
33
|
+
aes_gcm: AESGCM, data: bytes, aad: bytes | None = None
|
|
34
|
+
) -> bytes:
|
|
35
|
+
"""Encrypt data using AES-GCM with a random initialization vector.
|
|
36
|
+
|
|
37
|
+
Encrypts the provided data using AES-GCM and prepends a randomly
|
|
38
|
+
generated 12-byte IV to the ciphertext. The IV is required for
|
|
39
|
+
decryption.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
aes_gcm: An initialized AESGCM cipher instance.
|
|
43
|
+
data: The plaintext data to encrypt.
|
|
44
|
+
aad: Optional additional authenticated data. This data is not
|
|
45
|
+
encrypted but is authenticated, meaning any tampering will
|
|
46
|
+
be detected during decryption.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
The encrypted data with the IV prepended. Format: ``IV + ciphertext``.
|
|
50
|
+
|
|
51
|
+
Example:
|
|
52
|
+
>>> key = AESGCM.generate_key(bit_length=256)
|
|
53
|
+
>>> aes_gcm = AESGCM(key)
|
|
54
|
+
>>> encrypted = encrypt_with_aes_gcm(aes_gcm, b"hello world")
|
|
55
|
+
>>> len(encrypted) > len(b"hello world") # IV + ciphertext + tag
|
|
56
|
+
True
|
|
57
|
+
|
|
58
|
+
Note:
|
|
59
|
+
A new random IV is generated for each encryption call. Never reuse
|
|
60
|
+
an IV with the same key.
|
|
61
|
+
"""
|
|
62
|
+
iv = os.urandom(IV_LEN)
|
|
63
|
+
encrypted = aes_gcm.encrypt(iv, data, aad)
|
|
64
|
+
return iv + encrypted
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def decrypt_with_aes_gcm(
|
|
68
|
+
aes_gcm: AESGCM, data: bytes, aad: bytes | None = None
|
|
69
|
+
) -> bytes:
|
|
70
|
+
"""Decrypt data that was encrypted with AES-GCM.
|
|
71
|
+
|
|
72
|
+
Extracts the IV from the beginning of the encrypted data and uses it
|
|
73
|
+
to decrypt the ciphertext. Also verifies the authentication tag to
|
|
74
|
+
ensure data integrity.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
aes_gcm: An initialized AESGCM cipher instance with the same key
|
|
78
|
+
used for encryption.
|
|
79
|
+
data: The encrypted data with IV prepended (as returned by
|
|
80
|
+
``encrypt_with_aes_gcm``).
|
|
81
|
+
aad: Optional additional authenticated data. Must match the AAD
|
|
82
|
+
used during encryption, or decryption will fail.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
The decrypted plaintext data.
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
cryptography.exceptions.InvalidTag: If the authentication tag is
|
|
89
|
+
invalid, indicating the data was tampered with or the wrong
|
|
90
|
+
key/AAD was used.
|
|
91
|
+
|
|
92
|
+
Example:
|
|
93
|
+
>>> key = AESGCM.generate_key(bit_length=256)
|
|
94
|
+
>>> aes_gcm = AESGCM(key)
|
|
95
|
+
>>> encrypted = encrypt_with_aes_gcm(aes_gcm, b"secret")
|
|
96
|
+
>>> decrypt_with_aes_gcm(aes_gcm, encrypted)
|
|
97
|
+
b'secret'
|
|
98
|
+
"""
|
|
99
|
+
iv, encrypted = data[:IV_LEN], data[IV_LEN:]
|
|
100
|
+
return aes_gcm.decrypt(iv, encrypted, aad)
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Keyring utilities for secure storage and retrieval of secrets.
|
|
2
|
+
|
|
3
|
+
This module provides utility functions for working with the OS keyring,
|
|
4
|
+
including getting and creating cryptographic keys. Keys are stored
|
|
5
|
+
securely in the system's credential manager (e.g., macOS Keychain,
|
|
6
|
+
Windows Credential Manager, or Linux Secret Service).
|
|
7
|
+
|
|
8
|
+
When running in GitHub Actions, a plaintext keyring is used instead
|
|
9
|
+
since the system keyring is not available.
|
|
10
|
+
|
|
11
|
+
Example:
|
|
12
|
+
>>> from winiutils.src.security.keyring import get_or_create_fernet
|
|
13
|
+
>>> fernet, key_bytes = get_or_create_fernet("my_app", "encryption_key")
|
|
14
|
+
>>> encrypted = fernet.encrypt(b"secret data")
|
|
15
|
+
>>> fernet.decrypt(encrypted)
|
|
16
|
+
b'secret data'
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from base64 import b64decode, b64encode
|
|
20
|
+
from collections.abc import Callable
|
|
21
|
+
|
|
22
|
+
import keyring
|
|
23
|
+
from cryptography.fernet import Fernet
|
|
24
|
+
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
25
|
+
from pyrig.src.git.git import running_in_github_actions
|
|
26
|
+
|
|
27
|
+
if running_in_github_actions():
|
|
28
|
+
from keyrings.alt.file import PlaintextKeyring # type: ignore[import-untyped]
|
|
29
|
+
|
|
30
|
+
keyring.set_keyring(PlaintextKeyring())
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_or_create_fernet(service_name: str, username: str) -> tuple[Fernet, bytes]:
|
|
34
|
+
"""Get or create a Fernet symmetric encryption key from the keyring.
|
|
35
|
+
|
|
36
|
+
Retrieves an existing Fernet key from the system keyring, or generates
|
|
37
|
+
a new one if it doesn't exist. The key is stored in the keyring for
|
|
38
|
+
future use.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
service_name: The service name to use for keyring storage. This
|
|
42
|
+
identifies your application.
|
|
43
|
+
username: The username/key identifier within the service.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
A tuple of (Fernet instance, raw key bytes). The Fernet instance
|
|
47
|
+
can be used directly for encryption/decryption.
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
>>> fernet, key = get_or_create_fernet("my_app", "main_key")
|
|
51
|
+
>>> encrypted = fernet.encrypt(b"hello")
|
|
52
|
+
>>> fernet.decrypt(encrypted)
|
|
53
|
+
b'hello'
|
|
54
|
+
"""
|
|
55
|
+
return get_or_create_key(
|
|
56
|
+
service_name, username, Fernet, lambda: Fernet.generate_key()
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_or_create_aes_gcm(service_name: str, username: str) -> tuple[AESGCM, bytes]:
|
|
61
|
+
"""Get or create an AES-GCM encryption key from the keyring.
|
|
62
|
+
|
|
63
|
+
Retrieves an existing AES-GCM key from the system keyring, or generates
|
|
64
|
+
a new 256-bit key if it doesn't exist. The key is stored in the keyring
|
|
65
|
+
for future use.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
service_name: The service name to use for keyring storage. This
|
|
69
|
+
identifies your application.
|
|
70
|
+
username: The username/key identifier within the service.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
A tuple of (AESGCM instance, raw key bytes). The AESGCM instance
|
|
74
|
+
can be used with ``encrypt_with_aes_gcm`` and ``decrypt_with_aes_gcm``.
|
|
75
|
+
|
|
76
|
+
Example:
|
|
77
|
+
>>> aes_gcm, key = get_or_create_aes_gcm("my_app", "aes_key")
|
|
78
|
+
>>> from winiutils.src.security.cryptography import encrypt_with_aes_gcm
|
|
79
|
+
>>> encrypted = encrypt_with_aes_gcm(aes_gcm, b"secret")
|
|
80
|
+
"""
|
|
81
|
+
return get_or_create_key(
|
|
82
|
+
service_name, username, AESGCM, lambda: AESGCM.generate_key(bit_length=256)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def get_or_create_key[T](
|
|
87
|
+
service_name: str,
|
|
88
|
+
username: str,
|
|
89
|
+
key_class: Callable[[bytes], T],
|
|
90
|
+
generate_key_func: Callable[..., bytes],
|
|
91
|
+
) -> tuple[T, bytes]:
|
|
92
|
+
"""Get or create a cryptographic key from the keyring.
|
|
93
|
+
|
|
94
|
+
Generic function that retrieves an existing key from the system keyring,
|
|
95
|
+
or generates a new one using the provided generator function if it
|
|
96
|
+
doesn't exist.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
service_name: The service name to use for keyring storage.
|
|
100
|
+
username: The username/key identifier within the service.
|
|
101
|
+
key_class: A callable that takes raw key bytes and returns a cipher
|
|
102
|
+
instance (e.g., ``Fernet``, ``AESGCM``).
|
|
103
|
+
generate_key_func: A callable that generates new raw key bytes.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
A tuple of (cipher instance, raw key bytes).
|
|
107
|
+
|
|
108
|
+
Note:
|
|
109
|
+
Keys are stored in the keyring as base64-encoded strings. The
|
|
110
|
+
service name is modified to include the key class name to allow
|
|
111
|
+
storing different key types for the same service/username.
|
|
112
|
+
|
|
113
|
+
Example:
|
|
114
|
+
>>> from cryptography.fernet import Fernet
|
|
115
|
+
>>> cipher, key = get_or_create_key(
|
|
116
|
+
... "my_app",
|
|
117
|
+
... "custom_key",
|
|
118
|
+
... Fernet,
|
|
119
|
+
... Fernet.generate_key,
|
|
120
|
+
... )
|
|
121
|
+
"""
|
|
122
|
+
key = get_key_as_str(service_name, username, key_class)
|
|
123
|
+
if key is None:
|
|
124
|
+
binary_key = generate_key_func()
|
|
125
|
+
key = b64encode(binary_key).decode("ascii")
|
|
126
|
+
modified_service_name = make_service_name(service_name, key_class)
|
|
127
|
+
keyring.set_password(modified_service_name, username, key)
|
|
128
|
+
|
|
129
|
+
binary_key = b64decode(key)
|
|
130
|
+
return key_class(binary_key), binary_key
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def get_key_as_str[T](
|
|
134
|
+
service_name: str, username: str, key_class: Callable[[bytes], T]
|
|
135
|
+
) -> str | None:
|
|
136
|
+
"""Retrieve a key from the keyring as a base64-encoded string.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
service_name: The service name used for keyring storage.
|
|
140
|
+
username: The username/key identifier within the service.
|
|
141
|
+
key_class: The key class used to modify the service name.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
The base64-encoded key string, or None if the key doesn't exist.
|
|
145
|
+
"""
|
|
146
|
+
service_name = make_service_name(service_name, key_class)
|
|
147
|
+
return keyring.get_password(service_name, username)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def make_service_name[T](service_name: str, key_class: Callable[[bytes], T]) -> str:
|
|
151
|
+
"""Create a unique service name by combining service name and key class.
|
|
152
|
+
|
|
153
|
+
This allows storing different key types (Fernet, AESGCM, etc.) for the
|
|
154
|
+
same service and username combination.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
service_name: The base service name.
|
|
158
|
+
key_class: The key class whose name will be appended.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
A modified service name in the format ``{service_name}_{class_name}``.
|
|
162
|
+
|
|
163
|
+
Example:
|
|
164
|
+
>>> make_service_name("my_app", Fernet)
|
|
165
|
+
'my_app_Fernet'
|
|
166
|
+
"""
|
|
167
|
+
return f"{service_name}_{key_class.__name__}" # ty:ignore[unresolved-attribute]
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: winiutils
|
|
3
|
+
Version: 2.3.12
|
|
4
|
+
Summary: A utility library for Python development
|
|
5
|
+
Author: Winipedia
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
11
|
+
Requires-Dist: cryptography
|
|
12
|
+
Requires-Dist: defusedxml
|
|
13
|
+
Requires-Dist: keyring
|
|
14
|
+
Requires-Dist: keyrings-alt
|
|
15
|
+
Requires-Dist: polars
|
|
16
|
+
Requires-Dist: pyrig
|
|
17
|
+
Requires-Dist: tqdm
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# winiutils
|
|
22
|
+
|
|
23
|
+
<!-- tooling -->
|
|
24
|
+
[](https://github.com/Winipedia/pyrig)
|
|
25
|
+
[](https://github.com/astral-sh/uv)
|
|
26
|
+
[](https://pre-commit.com/)
|
|
27
|
+
<!-- code-quality -->
|
|
28
|
+
[](https://github.com/astral-sh/ruff)
|
|
29
|
+
[](https://github.com/astral-sh/ty)[](https://mypy-lang.org/)
|
|
30
|
+
[](https://github.com/PyCQA/bandit)
|
|
31
|
+
[](https://pytest.org/)
|
|
32
|
+
[](https://codecov.io/gh/Winipedia/winiutils)
|
|
33
|
+
<!-- package-info -->
|
|
34
|
+
[](https://pypi.org/project/winiutils/)
|
|
35
|
+
[](https://www.python.org/)
|
|
36
|
+
[](https://github.com/Winipedia/winiutils/blob/main/LICENSE)
|
|
37
|
+
<!-- ci/cd -->
|
|
38
|
+
[](https://github.com/Winipedia/winiutils/actions/workflows/health_check.yaml)
|
|
39
|
+
[](https://github.com/Winipedia/winiutils/actions/workflows/release.yaml)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
> A utility library for Python development
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## Table of Contents
|
|
50
|
+
|
|
51
|
+
- [Features](#features)
|
|
52
|
+
- [Installation](#installation)
|
|
53
|
+
- [Quick Start](#quick-start)
|
|
54
|
+
- [Documentation](#documentation)
|
|
55
|
+
- [Modules](#modules)
|
|
56
|
+
- [Development](#development)
|
|
57
|
+
- [License](#license)
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Features
|
|
62
|
+
|
|
63
|
+
- **DataFrame Cleaning Pipeline** — Extensible Polars DataFrame cleaning with an 8-step pipeline
|
|
64
|
+
- **Concurrent Processing** — Unified multiprocessing and multithreading with automatic resource optimization
|
|
65
|
+
- **OOP Utilities** — Metaclasses and mixins for automatic method logging and instrumentation
|
|
66
|
+
- **Security Tools** — OS keyring integration and AES-GCM encryption utilities
|
|
67
|
+
- **Type Safety** — Full type hints with strict mypy compliance
|
|
68
|
+
- **Production Ready** — Comprehensive test coverage and logging integration
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Installation
|
|
73
|
+
|
|
74
|
+
### Using uv (recommended)
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
uv add winiutils
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Using pip
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install winiutils
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### From source
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
git clone https://github.com/Winipedia/winiutils.git
|
|
90
|
+
cd winiutils
|
|
91
|
+
uv sync
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Quick Start
|
|
97
|
+
|
|
98
|
+
### DataFrame Cleaning
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from winiutils.src.data.dataframe.cleaning import CleaningDF
|
|
102
|
+
import polars as pl
|
|
103
|
+
|
|
104
|
+
class UserDataCleaner(CleaningDF):
|
|
105
|
+
"""Clean and standardize user data."""
|
|
106
|
+
|
|
107
|
+
USER_ID = "user_id"
|
|
108
|
+
EMAIL = "email"
|
|
109
|
+
SCORE = "score"
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def get_rename_map(cls):
|
|
113
|
+
return {cls.USER_ID: "UserId", cls.EMAIL: "Email", cls.SCORE: "Score"}
|
|
114
|
+
|
|
115
|
+
@classmethod
|
|
116
|
+
def get_col_dtype_map(cls):
|
|
117
|
+
return {cls.USER_ID: pl.Int64, cls.EMAIL: pl.Utf8, cls.SCORE: pl.Float64}
|
|
118
|
+
|
|
119
|
+
# ... implement other abstract methods
|
|
120
|
+
|
|
121
|
+
# Usage
|
|
122
|
+
cleaned = UserDataCleaner(raw_dataframe)
|
|
123
|
+
result = cleaned.df
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Concurrent Processing
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from winiutils.src.iterating.concurrent.multiprocessing import multiprocess_loop
|
|
130
|
+
from winiutils.src.iterating.concurrent.multithreading import multithread_loop
|
|
131
|
+
|
|
132
|
+
# CPU-bound tasks (multiprocessing)
|
|
133
|
+
def process_chunk(data, config):
|
|
134
|
+
return heavy_computation(data, config)
|
|
135
|
+
|
|
136
|
+
results = multiprocess_loop(
|
|
137
|
+
process_function=process_chunk,
|
|
138
|
+
process_args=[(chunk,) for chunk in data_chunks],
|
|
139
|
+
process_args_static=(config,),
|
|
140
|
+
process_args_len=len(data_chunks),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# I/O-bound tasks (multithreading)
|
|
144
|
+
def fetch_url(url, headers):
|
|
145
|
+
return requests.get(url, headers=headers)
|
|
146
|
+
|
|
147
|
+
results = multithread_loop(
|
|
148
|
+
process_function=fetch_url,
|
|
149
|
+
process_args=[(url,) for url in urls],
|
|
150
|
+
process_args_static=(headers,),
|
|
151
|
+
process_args_len=len(urls),
|
|
152
|
+
)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Automatic Method Logging
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from winiutils.src.oop.mixins.mixin import ABCLoggingMixin
|
|
159
|
+
|
|
160
|
+
class MyService(ABCLoggingMixin):
|
|
161
|
+
def process_data(self, data: list) -> dict:
|
|
162
|
+
# Automatically logged with timing
|
|
163
|
+
return {"processed": len(data)}
|
|
164
|
+
|
|
165
|
+
# Logs: "MyService - Calling process_data with (...) and {...}"
|
|
166
|
+
# Logs: "MyService - process_data finished with 0.5 seconds -> returning {...}"
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Encryption with Keyring
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from winiutils.src.security.keyring import get_or_create_aes_gcm
|
|
173
|
+
from winiutils.src.security.cryptography import encrypt_with_aes_gcm, decrypt_with_aes_gcm
|
|
174
|
+
|
|
175
|
+
# Get or create encryption key (stored in OS keyring)
|
|
176
|
+
aes_gcm, key = get_or_create_aes_gcm("my_app", "user@example.com")
|
|
177
|
+
|
|
178
|
+
# Encrypt and decrypt
|
|
179
|
+
encrypted = encrypt_with_aes_gcm(aes_gcm, b"Secret message")
|
|
180
|
+
decrypted = decrypt_with_aes_gcm(aes_gcm, encrypted)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Documentation
|
|
186
|
+
|
|
187
|
+
Full documentation is available in the [docs](./docs/) folder:
|
|
188
|
+
|
|
189
|
+
- [**Data Processing**](./docs/data.md) — DataFrame cleaning pipeline and data structures
|
|
190
|
+
- [**Iterating & Concurrency**](./docs/iterating.md) — Parallel processing utilities
|
|
191
|
+
- [**OOP Utilities**](./docs/oop.md) — Metaclasses and mixins
|
|
192
|
+
- [**Security**](./docs/security.md) — Encryption and keyring integration
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Modules
|
|
197
|
+
|
|
198
|
+
| Module | Description |
|
|
199
|
+
|--------|-------------|
|
|
200
|
+
| [`winiutils.src.data`](./docs/data.md) | DataFrame cleaning pipeline and data structure utilities |
|
|
201
|
+
| [`winiutils.src.iterating`](./docs/iterating.md) | Concurrent processing with multiprocessing and multithreading |
|
|
202
|
+
| [`winiutils.src.oop`](./docs/oop.md) | Metaclasses and mixins for automatic method logging |
|
|
203
|
+
| [`winiutils.src.security`](./docs/security.md) | AES-GCM encryption and OS keyring integration |
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Development
|
|
208
|
+
|
|
209
|
+
### Setup
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
git clone https://github.com/Winipedia/winiutils.git
|
|
213
|
+
cd winiutils
|
|
214
|
+
uv sync --all-groups
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Running Tests
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
uv run pytest
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Code Quality
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
# Linting
|
|
227
|
+
uv run ruff check .
|
|
228
|
+
|
|
229
|
+
# Type checking
|
|
230
|
+
uv run mypy .
|
|
231
|
+
|
|
232
|
+
# Security scanning
|
|
233
|
+
uv run bandit -r winiutils/
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Pre-commit Hooks
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
uv run pre-commit install
|
|
240
|
+
uv run pre-commit run --all-files
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Project Structure
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
winiutils/
|
|
249
|
+
├── src/ # Main source code
|
|
250
|
+
│ ├── data/ # Data processing
|
|
251
|
+
│ │ ├── dataframe/ # Polars DataFrame cleaning
|
|
252
|
+
│ │ └── structures/ # Dicts, text utilities
|
|
253
|
+
│ ├── iterating/ # Iteration utilities
|
|
254
|
+
│ │ └── concurrent/ # Multiprocessing & multithreading
|
|
255
|
+
│ ├── oop/ # OOP patterns
|
|
256
|
+
│ │ └── mixins/ # Logging metaclass & mixin
|
|
257
|
+
│ └── security/ # Security utilities
|
|
258
|
+
│ ├── cryptography.py # AES-GCM encryption
|
|
259
|
+
│ └── keyring.py # OS keyring integration
|
|
260
|
+
├── dev/ # Development tools
|
|
261
|
+
│ ├── cli/ # CLI subcommands
|
|
262
|
+
│ └── tests/fixtures/ # Test fixtures
|
|
263
|
+
├── docs/ # Documentation
|
|
264
|
+
└── tests/ # Test suite
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## License
|
|
270
|
+
|
|
271
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## Contributing
|
|
276
|
+
|
|
277
|
+
Contributions are welcome! Please ensure:
|
|
278
|
+
|
|
279
|
+
1. All tests pass (`uv run pytest`)
|
|
280
|
+
2. Code passes linting (`uv run ruff check .`)
|
|
281
|
+
3. Types are correct (`uv run mypy .`)
|
|
282
|
+
4. New features include tests
|
|
283
|
+
5. Documentation is updated for API changes
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
winiutils/__init__.py,sha256=vOWZ8n-YemVIzDLd8eWw1HVPGH3jxuT6VtDKHbmxk_A,43
|
|
2
|
+
winiutils/dev/__init__.py,sha256=XHsbmjiaGom-KX-S3leCY9cJD3aP9p_0X6xYMcdkHBU,23
|
|
3
|
+
winiutils/dev/builders/__init__.py,sha256=XHsbmjiaGom-KX-S3leCY9cJD3aP9p_0X6xYMcdkHBU,23
|
|
4
|
+
winiutils/dev/cli/__init__.py,sha256=XHsbmjiaGom-KX-S3leCY9cJD3aP9p_0X6xYMcdkHBU,23
|
|
5
|
+
winiutils/dev/cli/subcommands.py,sha256=iurWZwJwEKAfGpfjkn1YOhnRbIruCB4ouE-8R_Lh3JY,228
|
|
6
|
+
winiutils/dev/configs/__init__.py,sha256=XHsbmjiaGom-KX-S3leCY9cJD3aP9p_0X6xYMcdkHBU,23
|
|
7
|
+
winiutils/dev/tests/__init__.py,sha256=XHsbmjiaGom-KX-S3leCY9cJD3aP9p_0X6xYMcdkHBU,23
|
|
8
|
+
winiutils/dev/tests/fixtures/__init__.py,sha256=XHsbmjiaGom-KX-S3leCY9cJD3aP9p_0X6xYMcdkHBU,23
|
|
9
|
+
winiutils/dev/tests/fixtures/fixtures.py,sha256=oHJSjLS-oxS9r786S-wCCX5SQIbLt1rl2RjSLSW3S0A,937
|
|
10
|
+
winiutils/main.py,sha256=TdxQpUtNZIiYCsEdysMlQW-1UNy0__WTax-Ot5E2eYQ,144
|
|
11
|
+
winiutils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
winiutils/resources/__init__.py,sha256=XHsbmjiaGom-KX-S3leCY9cJD3aP9p_0X6xYMcdkHBU,23
|
|
13
|
+
winiutils/src/__init__.py,sha256=0-6q--PuFgSDe0_aXv_gih5f_2Q6kF9bmbT_RdpwNHA,86
|
|
14
|
+
winiutils/src/data/__init__.py,sha256=hpYEUhFBe-IImSDT13GqYbo1ebL2j1i-s836fm0kBZY,262
|
|
15
|
+
winiutils/src/data/dataframe/__init__.py,sha256=1oQTXgh9MR6ppUtLlcBxWSdyiu4AGYqZHHwaG8NQuu8,203
|
|
16
|
+
winiutils/src/data/dataframe/cleaning.py,sha256=dfecVWVmcjw0LSxFuGmvDAHiv3wMjncujJHDtSOi_V0,27589
|
|
17
|
+
winiutils/src/data/structures/__init__.py,sha256=zRTd-UHZLRH4LPSnQgKxNz2jcDT8Ah7x0gYfHxvKJEE,220
|
|
18
|
+
winiutils/src/data/structures/dicts.py,sha256=8R6Jw47G-Z8ad63Hsj0UUaaH6eM-tCur36zKU5urYKY,1190
|
|
19
|
+
winiutils/src/data/structures/text/__init__.py,sha256=8wUJOGa1-JLmdg0qaQzcuBOh99G2cyZvxO-T4qi9Ef0,195
|
|
20
|
+
winiutils/src/data/structures/text/string.py,sha256=Heg_Y6CNQ3p1ZaQjuz1ffBu-Vd0UHp143AnLLL5dQE0,5379
|
|
21
|
+
winiutils/src/iterating/__init__.py,sha256=25RIhD65SzjIkqRquxNHL8GMd3Cuvsw5EtG1ShYC64o,273
|
|
22
|
+
winiutils/src/iterating/concurrent/__init__.py,sha256=ymePluCroEVm16NJV-r7tCUCWUZcp7jvfKYOlW7AWgM,373
|
|
23
|
+
winiutils/src/iterating/concurrent/concurrent.py,sha256=EBvRuNG1XKubahZ405zX_j9c-iwwnS679GM03IP_cCQ,10717
|
|
24
|
+
winiutils/src/iterating/concurrent/multiprocessing.py,sha256=bz8CNwUonvK8sej0Yo_pCdlJuBWwlRaKI2CX6Txiad4,6509
|
|
25
|
+
winiutils/src/iterating/concurrent/multithreading.py,sha256=U3RN0vBdrbc8P059rP22yKE7umawjCbGlwN8CJd3TRg,4799
|
|
26
|
+
winiutils/src/iterating/iterate.py,sha256=CQP8YfkYywAGFVlkib_KZtgTy57bt0oC1KKwdDlH4vU,1554
|
|
27
|
+
winiutils/src/oop/__init__.py,sha256=HaR2nSm3P0Qj5iu9_8AtjDxeQS3fW17pZpgf2Yxyz80,232
|
|
28
|
+
winiutils/src/oop/mixins/__init__.py,sha256=kjKziiB-AKThhy7uHqKznfh7otIqW_a3z1bf22VVP84,270
|
|
29
|
+
winiutils/src/oop/mixins/meta.py,sha256=3hR5AGbqOPXumbytuDKRiW0VmBFuDENbrlwAjHCsdVg,7373
|
|
30
|
+
winiutils/src/oop/mixins/mixin.py,sha256=1DBFhyHWtuYR4pAE8t85nfx9McxcM2YvwQKCTSFugOY,2109
|
|
31
|
+
winiutils/src/security/__init__.py,sha256=6J6nWfzT2Xe2Yy7HHgj6dR2g9AYrqY77qL4aa_fzb94,250
|
|
32
|
+
winiutils/src/security/cryptography.py,sha256=Oz-5LK4p43kjnriZGJzeUxfZmKyxN5CnbxVrSwsk8lQ,3424
|
|
33
|
+
winiutils/src/security/keyring.py,sha256=PlEtJU2sJaM23IgLN1Tt93u3hNT_WsQiSw04TVfbuD8,6064
|
|
34
|
+
winiutils-2.3.12.dist-info/licenses/LICENSE,sha256=o316mE2gGzd__JT69p7S_zlOmKiHh8YjpImCCcWyTvM,1066
|
|
35
|
+
winiutils-2.3.12.dist-info/WHEEL,sha256=xDCZ-UyfvkGuEHPeI7BcJzYKIZzdqN8A8o1M5Om8IyA,79
|
|
36
|
+
winiutils-2.3.12.dist-info/entry_points.txt,sha256=oH6bkIotdZXXBGdxsd3xv_7LBil4P0Ph4-L6PFySkTw,95
|
|
37
|
+
winiutils-2.3.12.dist-info/METADATA,sha256=znX-DMTQHM9f2PifG0Tv1H9VNITAXT_QdlitorD7TXM,8670
|
|
38
|
+
winiutils-2.3.12.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Winipedia
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|