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.
Files changed (38) hide show
  1. winiutils/__init__.py +1 -0
  2. winiutils/dev/__init__.py +1 -0
  3. winiutils/dev/builders/__init__.py +1 -0
  4. winiutils/dev/cli/__init__.py +1 -0
  5. winiutils/dev/cli/subcommands.py +6 -0
  6. winiutils/dev/configs/__init__.py +1 -0
  7. winiutils/dev/tests/__init__.py +1 -0
  8. winiutils/dev/tests/fixtures/__init__.py +1 -0
  9. winiutils/dev/tests/fixtures/fixtures.py +32 -0
  10. winiutils/main.py +9 -0
  11. winiutils/py.typed +0 -0
  12. winiutils/resources/__init__.py +1 -0
  13. winiutils/src/__init__.py +4 -0
  14. winiutils/src/data/__init__.py +8 -0
  15. winiutils/src/data/dataframe/__init__.py +7 -0
  16. winiutils/src/data/dataframe/cleaning.py +734 -0
  17. winiutils/src/data/structures/__init__.py +8 -0
  18. winiutils/src/data/structures/dicts.py +40 -0
  19. winiutils/src/data/structures/text/__init__.py +7 -0
  20. winiutils/src/data/structures/text/string.py +157 -0
  21. winiutils/src/iterating/__init__.py +8 -0
  22. winiutils/src/iterating/concurrent/__init__.py +9 -0
  23. winiutils/src/iterating/concurrent/concurrent.py +301 -0
  24. winiutils/src/iterating/concurrent/multiprocessing.py +186 -0
  25. winiutils/src/iterating/concurrent/multithreading.py +132 -0
  26. winiutils/src/iterating/iterate.py +45 -0
  27. winiutils/src/oop/__init__.py +7 -0
  28. winiutils/src/oop/mixins/__init__.py +8 -0
  29. winiutils/src/oop/mixins/meta.py +217 -0
  30. winiutils/src/oop/mixins/mixin.py +58 -0
  31. winiutils/src/security/__init__.py +8 -0
  32. winiutils/src/security/cryptography.py +100 -0
  33. winiutils/src/security/keyring.py +167 -0
  34. winiutils-2.3.12.dist-info/METADATA +283 -0
  35. winiutils-2.3.12.dist-info/RECORD +38 -0
  36. winiutils-2.3.12.dist-info/WHEEL +4 -0
  37. winiutils-2.3.12.dist-info/entry_points.txt +4 -0
  38. 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
+ [![pyrig](https://img.shields.io/badge/built%20with-pyrig-3776AB?logo=buildkite&logoColor=black)](https://github.com/Winipedia/pyrig)
25
+ [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
26
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://pre-commit.com/)
27
+ <!-- code-quality -->
28
+ [![ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
29
+ [![ty](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ty/main/assets/badge/v0.json)](https://github.com/astral-sh/ty)[![mypy](https://img.shields.io/badge/type%20checked-mypy-039dfc.svg)](https://mypy-lang.org/)
30
+ [![security: bandit](https://img.shields.io/badge/security-bandit-yellow.svg)](https://github.com/PyCQA/bandit)
31
+ [![pytest](https://img.shields.io/badge/tested%20with-pytest-46a2f1.svg?logo=pytest)](https://pytest.org/)
32
+ [![codecov](https://codecov.io/gh/Winipedia/winiutils/branch/main/graph/badge.svg)](https://codecov.io/gh/Winipedia/winiutils)
33
+ <!-- package-info -->
34
+ [![PyPI](https://img.shields.io/pypi/v/winiutils?logo=pypi&logoColor=white)](https://pypi.org/project/winiutils/)
35
+ [![Python](https://img.shields.io/badge/python-3.12|3.13|3.14-blue.svg?logo=python&logoColor=white)](https://www.python.org/)
36
+ [![License](https://img.shields.io/github/license/Winipedia/winiutils)](https://github.com/Winipedia/winiutils/blob/main/LICENSE)
37
+ <!-- ci/cd -->
38
+ [![CI](https://img.shields.io/github/actions/workflow/status/Winipedia/winiutils/health_check.yaml?label=CI&logo=github)](https://github.com/Winipedia/winiutils/actions/workflows/health_check.yaml)
39
+ [![CD](https://img.shields.io/github/actions/workflow/status/Winipedia/winiutils/release.yaml?label=CD&logo=github)](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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.9.17
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,4 @@
1
+ [console_scripts]
2
+ winipedia-utils = pyrig.dev.cli.cli:main
3
+ winiutils = pyrig.dev.cli.cli:main
4
+
@@ -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.