GJDutils 0.2.2__tar.gz → 0.3.0__tar.gz
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.
- {gjdutils-0.2.2 → gjdutils-0.3.0}/.gitignore +1 -0
- gjdutils-0.3.0/PKG-INFO +273 -0
- gjdutils-0.3.0/README.md +205 -0
- gjdutils-0.3.0/__VERSION__.py +1 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/pyproject.toml +3 -0
- gjdutils-0.3.0/scripts/check_git_clean.py +38 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/scripts/deploy_pypiprod.py +5 -0
- gjdutils-0.3.0/scripts/export_all.sh +25 -0
- gjdutils-0.3.0/scripts/install_all_dev_dependencies.py +6 -0
- gjdutils-0.2.2/src/gjdutils/cacheing.py → gjdutils-0.3.0/src/gjdutils/caching.py +5 -2
- gjdutils-0.2.2/src/gjdutils/collections.py → gjdutils-0.3.0/src/gjdutils/collection_utils.py +11 -5
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/env.py +9 -1
- gjdutils-0.3.0/src/gjdutils/functions.py +38 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/google_translate.py +8 -11
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/hashing.py +2 -2
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/pypi_build.py +8 -0
- gjdutils-0.3.0/src/gjdutils/pytest_utils.py +24 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/rand.py +3 -3
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/runtime.py +3 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/voice_speechrecognition.py +10 -4
- gjdutils-0.3.0/src/gjdutils/vprint.py +20 -0
- gjdutils-0.3.0/tests/test_env_integration.py +60 -0
- gjdutils-0.3.0/tests/test_git_status.py +75 -0
- gjdutils-0.3.0/tests/test_vprint.py +34 -0
- gjdutils-0.2.2/PKG-INFO +0 -101
- gjdutils-0.2.2/README.md +0 -37
- gjdutils-0.2.2/__VERSION__.py +0 -1
- gjdutils-0.2.2/src/gjdutils/functions.py +0 -6
- gjdutils-0.2.2/src/gjdutils/pytest_utils.py +0 -24
- {gjdutils-0.2.2 → gjdutils-0.3.0}/LICENSE +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/docs/CODING_SHELL_SCRIPTS.md +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/planning/250215_publishing_to_pypi.md +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/planning/250215_rename_gdutils_to_gjdutils.md +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/scripts/check_locally.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/scripts/check_pypiprod.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/scripts/check_pypitest.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/scripts/deploy_all.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/scripts/deploy_pypitest.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/__init__.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/audios.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/cmd.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/colab.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/decorators.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/dicts.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/dsci.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/dt.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/errors.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/files.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/html.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/indexing.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/iterfunc.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/jsons.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/lists.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/llm_utils.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/llms_claude.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/llms_openai.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/misc.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/num.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/obsolete/google_text_to_speech.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/obsolete/llms_obsolete.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/outloud_text_to_speech.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/prompt_templates.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/regex.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/requirements_dev.txt +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/sets.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/shell.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/sorteddict.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/stopwatch.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/strings.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/todo/convert_parquet.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/typ.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/web.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/tests/__init__.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/tests/test_indexing.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/tests/test_rand.py +0 -0
- {gjdutils-0.2.2 → gjdutils-0.3.0}/tests/test_strings.py +0 -0
gjdutils-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: GJDutils
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A collection of useful utility functions (basics, data science/AI, web development, etc)
|
|
5
|
+
Project-URL: Homepage, https://github.com/gregdetre/gjdutils
|
|
6
|
+
Project-URL: Repository, https://github.com/gregdetre/gjdutils
|
|
7
|
+
Author-email: Greg Detre <greg@gregdetre.com>
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: ai,data science,dates,llm,strings,utilities,web development
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Utilities
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Requires-Dist: ipython
|
|
18
|
+
Requires-Dist: jinja2
|
|
19
|
+
Requires-Dist: pydantic
|
|
20
|
+
Requires-Dist: python-dotenv
|
|
21
|
+
Provides-Extra: all-no-dev
|
|
22
|
+
Requires-Dist: azure-cognitiveservices-speech; extra == 'all-no-dev'
|
|
23
|
+
Requires-Dist: bs4; extra == 'all-no-dev'
|
|
24
|
+
Requires-Dist: cachetools; extra == 'all-no-dev'
|
|
25
|
+
Requires-Dist: elevenlabs; extra == 'all-no-dev'
|
|
26
|
+
Requires-Dist: google-cloud-texttospeech; extra == 'all-no-dev'
|
|
27
|
+
Requires-Dist: google-cloud-translate; extra == 'all-no-dev'
|
|
28
|
+
Requires-Dist: humanize; extra == 'all-no-dev'
|
|
29
|
+
Requires-Dist: openai; extra == 'all-no-dev'
|
|
30
|
+
Requires-Dist: openai-whisper; extra == 'all-no-dev'
|
|
31
|
+
Requires-Dist: pendulum; extra == 'all-no-dev'
|
|
32
|
+
Requires-Dist: pillow; extra == 'all-no-dev'
|
|
33
|
+
Requires-Dist: playsound; extra == 'all-no-dev'
|
|
34
|
+
Requires-Dist: pyaudio; extra == 'all-no-dev'
|
|
35
|
+
Requires-Dist: pygame; extra == 'all-no-dev'
|
|
36
|
+
Requires-Dist: python-vlc; extra == 'all-no-dev'
|
|
37
|
+
Requires-Dist: speechrecognition; extra == 'all-no-dev'
|
|
38
|
+
Provides-Extra: audio-lang
|
|
39
|
+
Requires-Dist: azure-cognitiveservices-speech; extra == 'audio-lang'
|
|
40
|
+
Requires-Dist: cachetools; extra == 'audio-lang'
|
|
41
|
+
Requires-Dist: elevenlabs; extra == 'audio-lang'
|
|
42
|
+
Requires-Dist: google-cloud-texttospeech; extra == 'audio-lang'
|
|
43
|
+
Requires-Dist: google-cloud-translate; extra == 'audio-lang'
|
|
44
|
+
Requires-Dist: openai-whisper; extra == 'audio-lang'
|
|
45
|
+
Requires-Dist: playsound; extra == 'audio-lang'
|
|
46
|
+
Requires-Dist: pyaudio; extra == 'audio-lang'
|
|
47
|
+
Requires-Dist: pygame; extra == 'audio-lang'
|
|
48
|
+
Requires-Dist: python-vlc; extra == 'audio-lang'
|
|
49
|
+
Requires-Dist: speechrecognition; extra == 'audio-lang'
|
|
50
|
+
Provides-Extra: dev
|
|
51
|
+
Requires-Dist: black; extra == 'dev'
|
|
52
|
+
Requires-Dist: build; extra == 'dev'
|
|
53
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
54
|
+
Requires-Dist: rich; extra == 'dev'
|
|
55
|
+
Requires-Dist: twine; extra == 'dev'
|
|
56
|
+
Requires-Dist: typer; extra == 'dev'
|
|
57
|
+
Requires-Dist: wheel; extra == 'dev'
|
|
58
|
+
Provides-Extra: dt
|
|
59
|
+
Requires-Dist: humanize; extra == 'dt'
|
|
60
|
+
Requires-Dist: pendulum; extra == 'dt'
|
|
61
|
+
Provides-Extra: html-web
|
|
62
|
+
Requires-Dist: bs4; extra == 'html-web'
|
|
63
|
+
Provides-Extra: llm
|
|
64
|
+
Requires-Dist: cachetools; extra == 'llm'
|
|
65
|
+
Requires-Dist: openai; extra == 'llm'
|
|
66
|
+
Requires-Dist: pillow; extra == 'llm'
|
|
67
|
+
Description-Content-Type: text/markdown
|
|
68
|
+
|
|
69
|
+
# gjdutils
|
|
70
|
+
|
|
71
|
+
A collection of useful utility functions (strings, dates, data science/AI, web development, types, etc).
|
|
72
|
+
|
|
73
|
+
This is a smorgasbord of utility functions, patterns and convenient wrappers that I've found myself rewriting and reusing across multiple projects, gathered into one place.
|
|
74
|
+
|
|
75
|
+
Probably many of these exist elsewhere in libraries - if so, please let me know, because I'd probably rather use something cleaner and better-maintained.
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
## Highlights
|
|
79
|
+
|
|
80
|
+
### Audio: convenient microphone voice recognition with Whisper, and text-to-speech using ElevenLabs
|
|
81
|
+
```python
|
|
82
|
+
from gjdutils.outloud_text_to_speech import outloud
|
|
83
|
+
from gjdutils.voice_speechrecognition import recognise_speech
|
|
84
|
+
|
|
85
|
+
# Record speech and play it back in a different voice
|
|
86
|
+
text = recognise_speech("Say something!") # Records from microphone until you press ENTER
|
|
87
|
+
outloud(text, prog="elevenlabs", mp3_filen="recording.mp3", should_play=True) # Plays back what you said
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
### Run shell commands clearly & conveniently
|
|
92
|
+
```python
|
|
93
|
+
from gjdutils.cmd import run_cmd
|
|
94
|
+
from pathlib import Path
|
|
95
|
+
|
|
96
|
+
# Get Python version and capture the output
|
|
97
|
+
retcode, stdout, extra = run_cmd(
|
|
98
|
+
"python --version", # you can also provide as a list-of-strings
|
|
99
|
+
before_msg="Checking Python version...",
|
|
100
|
+
fatal_msg="Some problem running Python", # will show up in red
|
|
101
|
+
verbose=0, # Run silently unless there's an error
|
|
102
|
+
**{"timeout": 5} # Pass additional arguments to subprocess
|
|
103
|
+
)
|
|
104
|
+
print(f"Python version: {stdout}") # e.g. "Python 3.9.7"
|
|
105
|
+
print(f"Ran command: {extra['cmd_str']}") # plus lots of other stuff stored
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
### Environment variables with type validation and helpful error messages
|
|
110
|
+
```bash
|
|
111
|
+
$ source scripts/export_all.sh .env
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from gjdutils.env import get_env_var
|
|
116
|
+
|
|
117
|
+
api_key = get_env_var("OPENAI_API_KEY") # Ensures non-empty by default
|
|
118
|
+
num_workers = get_env_var("NUM_WORKERS", typ=int) # Validates and converts to int
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Strict Jinja templating that catches both undefined and unused variables
|
|
122
|
+
```python
|
|
123
|
+
from gjdutils.strings import jinja_render
|
|
124
|
+
|
|
125
|
+
template = "{{name}} is {{age}} years old"
|
|
126
|
+
context = {"name": "Bob", "unused": True}
|
|
127
|
+
text = jinja_render(template, context) # will fail both because `age` is missing and `unused` is superfluous
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
### Set random seeds across Python, NumPy, PyTorch for reproducibility
|
|
132
|
+
```python
|
|
133
|
+
from gjdutils.rand import set_seeds
|
|
134
|
+
|
|
135
|
+
set_seeds(42) # Sets seeds for random, numpy, torch if available
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Call Claude/OpenAI APIs with function calling, image analysis & JSON support
|
|
139
|
+
```python
|
|
140
|
+
from gjdutils.llms_claude import call_claude_gpt
|
|
141
|
+
from gjdutils.llm_utils import image_to_base64
|
|
142
|
+
|
|
143
|
+
response, extra = call_claude_gpt(
|
|
144
|
+
"What's in this image?",
|
|
145
|
+
image_filens=["path/to/image.jpg"], # Can pass multiple images
|
|
146
|
+
temperature=0.001
|
|
147
|
+
)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
### Translate text between languages with Google Translate
|
|
152
|
+
```python
|
|
153
|
+
from gjdutils.google_translate import translate_text, detect_language
|
|
154
|
+
|
|
155
|
+
# First detect the language
|
|
156
|
+
text = "Bonjour le monde"
|
|
157
|
+
lang, confidence = detect_language(text) # Returns ("fr", 0.98)
|
|
158
|
+
|
|
159
|
+
# Then translate to English
|
|
160
|
+
english_text, _ = translate_text(text, lang_src_code=lang, lang_tgt_code="en") # Returns "Hello world"
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
### Calculate text similarity using longest common substring analysis
|
|
165
|
+
```python
|
|
166
|
+
from gjdutils.strings import calc_proportion_longest_common_substring
|
|
167
|
+
similarity = calc_proportion_longest_common_substring(["hello world", "hello there"]) # Returns ~0.45 for "hello" match
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
### Measure data uniformity & distribution with simple proportion analysis
|
|
172
|
+
```python
|
|
173
|
+
from gjdutils.dsci import calc_proportion_identical
|
|
174
|
+
uniformity = calc_proportion_identical(['a', 'a', 'a', 'b']) # Returns 0.75 (75% are 'a')
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
### Generate deterministic cache keys for complex Python objects
|
|
180
|
+
```python
|
|
181
|
+
from gjdutils.caching import generate_mckey
|
|
182
|
+
cache_key = generate_mckey("myprefix", {"user_id": 123, "action": "login"}) # Creates deterministic cache key
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
### Generate consistent hashes for caching/comparison
|
|
187
|
+
```python
|
|
188
|
+
from gjdutils.hashing import hash_readable
|
|
189
|
+
|
|
190
|
+
# Same input always produces same hash, even across sessions
|
|
191
|
+
config = {"model": "gpt-4", "temperature": 0.7}
|
|
192
|
+
cache_key = hash_readable(config) # e.g. "8f4e5d3..."
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
### Pretty-print and process HTML with customizable indentation
|
|
197
|
+
```python
|
|
198
|
+
from gjdutils.html import prettify_html
|
|
199
|
+
|
|
200
|
+
# Prettify a string of HTML (also useful for testing two HTML strings are identical without caring about whitespace)
|
|
201
|
+
html = "<div><p>Hello</p><p>World</p></div>" # Also works with BeautifulSoup elements
|
|
202
|
+
pretty = prettify_html(html, indent=4) # Custom indentation
|
|
203
|
+
print(pretty)
|
|
204
|
+
# <div>
|
|
205
|
+
# <p>Hello</p>
|
|
206
|
+
# <p>World</p>
|
|
207
|
+
# </div>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Debug by printing local variables, excluding noise
|
|
211
|
+
```python
|
|
212
|
+
from gjdutils.misc import print_locals
|
|
213
|
+
|
|
214
|
+
def my_function(x, y):
|
|
215
|
+
z = x + y
|
|
216
|
+
some_func = lambda x: x * 2
|
|
217
|
+
_internal = "temp"
|
|
218
|
+
# Print all local vars except functions and _prefixed
|
|
219
|
+
print_locals(locals(), ignore_functions=True, ignore_underscores=True)
|
|
220
|
+
# Output: {'x': 1, 'y': 2, 'z': 3}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Generate readable random IDs (no confusing characters)
|
|
224
|
+
```python
|
|
225
|
+
from gjdutils.rand import gen_readable_rand_id
|
|
226
|
+
|
|
227
|
+
# Generate random ID without confusing chars (0/O, 1/I/l, etc)
|
|
228
|
+
uid = gen_readable_rand_id(n=7) # e.g. "k8m5p3h"
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
----
|
|
232
|
+
|
|
233
|
+
## Installation
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
pip install gjdutils
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
For optional features:
|
|
240
|
+
```bash
|
|
241
|
+
pip install "gjdutils[dt]" # Date/time utilities
|
|
242
|
+
pip install "gjdutils[llm]" # AI/LLM integrations
|
|
243
|
+
pip install "gjdutils[audio_lang]" # Speech/translation, language-related
|
|
244
|
+
pip install "gjdutils[html_web]" # Web scraping
|
|
245
|
+
|
|
246
|
+
pip install "gjdutils[dev]" # Development tools (for tweaking `gjdutils` itself, e.g. pytest)
|
|
247
|
+
|
|
248
|
+
# Install all optional dependencies at once (except `dev`, which is used for developing `gjdutils` itself)
|
|
249
|
+
pip install "gjdutils[all_no_dev]"
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Development Setup
|
|
253
|
+
|
|
254
|
+
If you're developing `gjdutils` itself, install in editable mode:
|
|
255
|
+
```bash
|
|
256
|
+
# (Assumes you have already setup your virtualenv)
|
|
257
|
+
# from the gjdutils root directory
|
|
258
|
+
pip install -e ".[all_no_dev, dev]" # Install all optional dependencies
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Or if you're feeling lazy and can't remember that command, just use:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
python scripts/install_all_dev_dependencies.py
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
### Adding to requirements.txt
|
|
269
|
+
|
|
270
|
+
To add to your `requirements.txt` in editable mode, e.g. to install all optional dependencies:
|
|
271
|
+
```text
|
|
272
|
+
-e "git+https://github.com/gregdetre/gjdutils.git#egg=gjdutils[all_no_dev]"
|
|
273
|
+
```
|
gjdutils-0.3.0/README.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# gjdutils
|
|
2
|
+
|
|
3
|
+
A collection of useful utility functions (strings, dates, data science/AI, web development, types, etc).
|
|
4
|
+
|
|
5
|
+
This is a smorgasbord of utility functions, patterns and convenient wrappers that I've found myself rewriting and reusing across multiple projects, gathered into one place.
|
|
6
|
+
|
|
7
|
+
Probably many of these exist elsewhere in libraries - if so, please let me know, because I'd probably rather use something cleaner and better-maintained.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Highlights
|
|
11
|
+
|
|
12
|
+
### Audio: convenient microphone voice recognition with Whisper, and text-to-speech using ElevenLabs
|
|
13
|
+
```python
|
|
14
|
+
from gjdutils.outloud_text_to_speech import outloud
|
|
15
|
+
from gjdutils.voice_speechrecognition import recognise_speech
|
|
16
|
+
|
|
17
|
+
# Record speech and play it back in a different voice
|
|
18
|
+
text = recognise_speech("Say something!") # Records from microphone until you press ENTER
|
|
19
|
+
outloud(text, prog="elevenlabs", mp3_filen="recording.mp3", should_play=True) # Plays back what you said
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Run shell commands clearly & conveniently
|
|
24
|
+
```python
|
|
25
|
+
from gjdutils.cmd import run_cmd
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
# Get Python version and capture the output
|
|
29
|
+
retcode, stdout, extra = run_cmd(
|
|
30
|
+
"python --version", # you can also provide as a list-of-strings
|
|
31
|
+
before_msg="Checking Python version...",
|
|
32
|
+
fatal_msg="Some problem running Python", # will show up in red
|
|
33
|
+
verbose=0, # Run silently unless there's an error
|
|
34
|
+
**{"timeout": 5} # Pass additional arguments to subprocess
|
|
35
|
+
)
|
|
36
|
+
print(f"Python version: {stdout}") # e.g. "Python 3.9.7"
|
|
37
|
+
print(f"Ran command: {extra['cmd_str']}") # plus lots of other stuff stored
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
### Environment variables with type validation and helpful error messages
|
|
42
|
+
```bash
|
|
43
|
+
$ source scripts/export_all.sh .env
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from gjdutils.env import get_env_var
|
|
48
|
+
|
|
49
|
+
api_key = get_env_var("OPENAI_API_KEY") # Ensures non-empty by default
|
|
50
|
+
num_workers = get_env_var("NUM_WORKERS", typ=int) # Validates and converts to int
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Strict Jinja templating that catches both undefined and unused variables
|
|
54
|
+
```python
|
|
55
|
+
from gjdutils.strings import jinja_render
|
|
56
|
+
|
|
57
|
+
template = "{{name}} is {{age}} years old"
|
|
58
|
+
context = {"name": "Bob", "unused": True}
|
|
59
|
+
text = jinja_render(template, context) # will fail both because `age` is missing and `unused` is superfluous
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
### Set random seeds across Python, NumPy, PyTorch for reproducibility
|
|
64
|
+
```python
|
|
65
|
+
from gjdutils.rand import set_seeds
|
|
66
|
+
|
|
67
|
+
set_seeds(42) # Sets seeds for random, numpy, torch if available
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Call Claude/OpenAI APIs with function calling, image analysis & JSON support
|
|
71
|
+
```python
|
|
72
|
+
from gjdutils.llms_claude import call_claude_gpt
|
|
73
|
+
from gjdutils.llm_utils import image_to_base64
|
|
74
|
+
|
|
75
|
+
response, extra = call_claude_gpt(
|
|
76
|
+
"What's in this image?",
|
|
77
|
+
image_filens=["path/to/image.jpg"], # Can pass multiple images
|
|
78
|
+
temperature=0.001
|
|
79
|
+
)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
### Translate text between languages with Google Translate
|
|
84
|
+
```python
|
|
85
|
+
from gjdutils.google_translate import translate_text, detect_language
|
|
86
|
+
|
|
87
|
+
# First detect the language
|
|
88
|
+
text = "Bonjour le monde"
|
|
89
|
+
lang, confidence = detect_language(text) # Returns ("fr", 0.98)
|
|
90
|
+
|
|
91
|
+
# Then translate to English
|
|
92
|
+
english_text, _ = translate_text(text, lang_src_code=lang, lang_tgt_code="en") # Returns "Hello world"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
### Calculate text similarity using longest common substring analysis
|
|
97
|
+
```python
|
|
98
|
+
from gjdutils.strings import calc_proportion_longest_common_substring
|
|
99
|
+
similarity = calc_proportion_longest_common_substring(["hello world", "hello there"]) # Returns ~0.45 for "hello" match
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
### Measure data uniformity & distribution with simple proportion analysis
|
|
104
|
+
```python
|
|
105
|
+
from gjdutils.dsci import calc_proportion_identical
|
|
106
|
+
uniformity = calc_proportion_identical(['a', 'a', 'a', 'b']) # Returns 0.75 (75% are 'a')
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
### Generate deterministic cache keys for complex Python objects
|
|
112
|
+
```python
|
|
113
|
+
from gjdutils.caching import generate_mckey
|
|
114
|
+
cache_key = generate_mckey("myprefix", {"user_id": 123, "action": "login"}) # Creates deterministic cache key
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
### Generate consistent hashes for caching/comparison
|
|
119
|
+
```python
|
|
120
|
+
from gjdutils.hashing import hash_readable
|
|
121
|
+
|
|
122
|
+
# Same input always produces same hash, even across sessions
|
|
123
|
+
config = {"model": "gpt-4", "temperature": 0.7}
|
|
124
|
+
cache_key = hash_readable(config) # e.g. "8f4e5d3..."
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
### Pretty-print and process HTML with customizable indentation
|
|
129
|
+
```python
|
|
130
|
+
from gjdutils.html import prettify_html
|
|
131
|
+
|
|
132
|
+
# Prettify a string of HTML (also useful for testing two HTML strings are identical without caring about whitespace)
|
|
133
|
+
html = "<div><p>Hello</p><p>World</p></div>" # Also works with BeautifulSoup elements
|
|
134
|
+
pretty = prettify_html(html, indent=4) # Custom indentation
|
|
135
|
+
print(pretty)
|
|
136
|
+
# <div>
|
|
137
|
+
# <p>Hello</p>
|
|
138
|
+
# <p>World</p>
|
|
139
|
+
# </div>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Debug by printing local variables, excluding noise
|
|
143
|
+
```python
|
|
144
|
+
from gjdutils.misc import print_locals
|
|
145
|
+
|
|
146
|
+
def my_function(x, y):
|
|
147
|
+
z = x + y
|
|
148
|
+
some_func = lambda x: x * 2
|
|
149
|
+
_internal = "temp"
|
|
150
|
+
# Print all local vars except functions and _prefixed
|
|
151
|
+
print_locals(locals(), ignore_functions=True, ignore_underscores=True)
|
|
152
|
+
# Output: {'x': 1, 'y': 2, 'z': 3}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Generate readable random IDs (no confusing characters)
|
|
156
|
+
```python
|
|
157
|
+
from gjdutils.rand import gen_readable_rand_id
|
|
158
|
+
|
|
159
|
+
# Generate random ID without confusing chars (0/O, 1/I/l, etc)
|
|
160
|
+
uid = gen_readable_rand_id(n=7) # e.g. "k8m5p3h"
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
----
|
|
164
|
+
|
|
165
|
+
## Installation
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
pip install gjdutils
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
For optional features:
|
|
172
|
+
```bash
|
|
173
|
+
pip install "gjdutils[dt]" # Date/time utilities
|
|
174
|
+
pip install "gjdutils[llm]" # AI/LLM integrations
|
|
175
|
+
pip install "gjdutils[audio_lang]" # Speech/translation, language-related
|
|
176
|
+
pip install "gjdutils[html_web]" # Web scraping
|
|
177
|
+
|
|
178
|
+
pip install "gjdutils[dev]" # Development tools (for tweaking `gjdutils` itself, e.g. pytest)
|
|
179
|
+
|
|
180
|
+
# Install all optional dependencies at once (except `dev`, which is used for developing `gjdutils` itself)
|
|
181
|
+
pip install "gjdutils[all_no_dev]"
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Development Setup
|
|
185
|
+
|
|
186
|
+
If you're developing `gjdutils` itself, install in editable mode:
|
|
187
|
+
```bash
|
|
188
|
+
# (Assumes you have already setup your virtualenv)
|
|
189
|
+
# from the gjdutils root directory
|
|
190
|
+
pip install -e ".[all_no_dev, dev]" # Install all optional dependencies
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Or if you're feeling lazy and can't remember that command, just use:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
python scripts/install_all_dev_dependencies.py
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
### Adding to requirements.txt
|
|
201
|
+
|
|
202
|
+
To add to your `requirements.txt` in editable mode, e.g. to install all optional dependencies:
|
|
203
|
+
```text
|
|
204
|
+
-e "git+https://github.com/gregdetre/gjdutils.git#egg=gjdutils[all_no_dev]"
|
|
205
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.0"
|
|
@@ -37,11 +37,14 @@ audio_lang = [
|
|
|
37
37
|
"elevenlabs",
|
|
38
38
|
# for playing mp3
|
|
39
39
|
"playsound",
|
|
40
|
+
# for voice_speechrecognition Microphone
|
|
41
|
+
"pyaudio",
|
|
40
42
|
# for playing mp3
|
|
41
43
|
"pygame",
|
|
42
44
|
# for playing mp3
|
|
43
45
|
"python-vlc",
|
|
44
46
|
"SpeechRecognition",
|
|
47
|
+
"openai-whisper", # for OpenAI Whisper speech recognition
|
|
45
48
|
]
|
|
46
49
|
dev = [
|
|
47
50
|
"black",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from gjdutils.shell import fatal_error_msg
|
|
5
|
+
from gjdutils.cmd import run_cmd
|
|
6
|
+
|
|
7
|
+
console = Console()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def check_git_status():
|
|
11
|
+
"""Check if git working directory is clean."""
|
|
12
|
+
# Check for unstaged changes
|
|
13
|
+
retcode, stdout, _ = run_cmd("git diff --quiet", check=False)
|
|
14
|
+
if retcode != 0:
|
|
15
|
+
_, diff_output, _ = run_cmd("git --no-pager diff --stat")
|
|
16
|
+
fatal_error_msg("Unstaged changes present:\n" + diff_output)
|
|
17
|
+
|
|
18
|
+
# Check for staged but uncommitted changes
|
|
19
|
+
retcode, stdout, _ = run_cmd("git diff --cached --quiet", check=False)
|
|
20
|
+
if retcode != 0:
|
|
21
|
+
_, diff_output, _ = run_cmd("git --no-pager diff --cached --stat")
|
|
22
|
+
fatal_error_msg("Uncommitted staged changes present:\n" + diff_output)
|
|
23
|
+
|
|
24
|
+
# Check for untracked files
|
|
25
|
+
_, untracked, _ = run_cmd("git ls-files --others --exclude-standard")
|
|
26
|
+
if untracked.strip():
|
|
27
|
+
fatal_error_msg(f"Untracked files present:\n{untracked}")
|
|
28
|
+
|
|
29
|
+
console.print("[green]Git: clean[/green]")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def main():
|
|
33
|
+
console.rule("[yellow]Checking Git Status")
|
|
34
|
+
check_git_status()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if __name__ == "__main__":
|
|
38
|
+
main()
|
|
@@ -13,6 +13,8 @@ from gjdutils.pypi_build import (
|
|
|
13
13
|
upload_to_pypi,
|
|
14
14
|
)
|
|
15
15
|
from gjdutils.shell import fatal_error_msg
|
|
16
|
+
from gjdutils.cmd import run_cmd
|
|
17
|
+
from scripts.check_git_clean import check_git_status
|
|
16
18
|
|
|
17
19
|
console = Console()
|
|
18
20
|
|
|
@@ -20,6 +22,9 @@ console = Console()
|
|
|
20
22
|
def main():
|
|
21
23
|
console.rule("[yellow]Starting Production PyPI Deployment")
|
|
22
24
|
|
|
25
|
+
# Check git status first
|
|
26
|
+
check_git_status()
|
|
27
|
+
|
|
23
28
|
# Check if version already exists
|
|
24
29
|
if check_version_exists(Version(__version__), pypi_env="prod"):
|
|
25
30
|
fatal_error_msg(
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# https://stackoverflow.com/a/20909045/230523
|
|
4
|
+
# e.g. source scripts/export_all.sh .env && echo $TESTME
|
|
5
|
+
|
|
6
|
+
# Check if the script is being sourced
|
|
7
|
+
if [ "$0" = "$BASH_SOURCE" ]; then
|
|
8
|
+
echo "Error: This script needs to be sourced. Please run: source $0 <env-file>"
|
|
9
|
+
exit 1
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
if [ $# -eq 0 ]; then
|
|
13
|
+
echo "Error: Environment file path is required"
|
|
14
|
+
echo "Usage: source $0 <env-file>"
|
|
15
|
+
return 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
ENV_FILE="$1"
|
|
19
|
+
|
|
20
|
+
if [ ! -f "$ENV_FILE" ]; then
|
|
21
|
+
echo "Error: File '$ENV_FILE' does not exist"
|
|
22
|
+
return 1
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
export $(grep -v '^#' "$ENV_FILE" | xargs)
|
|
@@ -2,6 +2,9 @@ from functools import update_wrapper
|
|
|
2
2
|
import inspect
|
|
3
3
|
import re
|
|
4
4
|
|
|
5
|
+
from gjdutils.sorteddict import SortedDict
|
|
6
|
+
from gjdutils.typ import isiterable
|
|
7
|
+
|
|
5
8
|
|
|
6
9
|
# from abracadjabra.utils
|
|
7
10
|
def generate_mckey(prefix, d):
|
|
@@ -42,13 +45,13 @@ def generate_mckey(prefix, d):
|
|
|
42
45
|
Returns a SortedDict, with the keys sorted alphabetically.
|
|
43
46
|
|
|
44
47
|
This might not be necessary, since I think the order
|
|
45
|
-
of a
|
|
48
|
+
of a Python 3 dict's keys() is deterministic, but by
|
|
46
49
|
sorting by dictionary keys, it's easier to know in
|
|
47
50
|
advance what the generated MCKEY should look like.
|
|
48
51
|
The idea is to ensure that no matter how D was
|
|
49
52
|
created, you'll know what the key should be.
|
|
50
53
|
"""
|
|
51
|
-
sorted_d = SortedDict()
|
|
54
|
+
sorted_d = SortedDict(lambda x: x)
|
|
52
55
|
for k in sorted(d.keys()):
|
|
53
56
|
sorted_d[k] = d[k]
|
|
54
57
|
return sorted_d
|
gjdutils-0.2.2/src/gjdutils/collections.py → gjdutils-0.3.0/src/gjdutils/collection_utils.py
RENAMED
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
from
|
|
2
|
-
from typing import Any, Callable, Literal, Sequence, TypeVar
|
|
1
|
+
from typing import Literal, Sequence, TypeVar
|
|
3
2
|
|
|
4
3
|
T = TypeVar("T")
|
|
5
4
|
|
|
6
5
|
|
|
7
6
|
def found_one(lst: Sequence[T]) -> T | Literal[False]:
|
|
7
|
+
"""
|
|
8
|
+
e.g.
|
|
9
|
+
>>> found_one([])
|
|
10
|
+
False
|
|
11
|
+
>>> found_one([10])
|
|
12
|
+
10
|
|
13
|
+
>>> found_one([10, 20])
|
|
14
|
+
False
|
|
15
|
+
"""
|
|
8
16
|
if len(lst) == 0:
|
|
9
17
|
return False
|
|
10
18
|
elif len(lst) == 1:
|
|
@@ -16,9 +24,7 @@ def found_one(lst: Sequence[T]) -> T | Literal[False]:
|
|
|
16
24
|
|
|
17
25
|
|
|
18
26
|
def find_duplicates(lst: Sequence[T]) -> list[T]:
|
|
19
|
-
return [item for item in lst if lst.count(item) > 1]
|
|
20
|
-
|
|
21
|
-
from collections import OrderedDict
|
|
27
|
+
return [item for item in set(lst) if lst.count(item) > 1]
|
|
22
28
|
|
|
23
29
|
|
|
24
30
|
# def uniquify(items: Sequence[T], key: Callable[[T], Any] | None = None) -> list[T]:
|
|
@@ -3,11 +3,17 @@ from pathlib import Path
|
|
|
3
3
|
from typing import Any, TypeVar, cast
|
|
4
4
|
from pydantic import StrictStr, TypeAdapter
|
|
5
5
|
|
|
6
|
+
from gjdutils.vprint import vprint
|
|
7
|
+
|
|
6
8
|
T = TypeVar("T")
|
|
7
9
|
_processed_vars = set()
|
|
8
10
|
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
# You may find it useful to run `scripts/export_all.sh .env` to first
|
|
13
|
+
# export all the variables in your .env file to your environment.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_env_var(name: str, typ: Any = StrictStr, verbose: int = 0) -> T:
|
|
11
17
|
"""Get environment variable with type validation, e.g.
|
|
12
18
|
|
|
13
19
|
OPENAI_API_KEY = get_env_var("OPENAI_API_KEY")
|
|
@@ -23,6 +29,8 @@ def get_env_var(name: str, typ: Any = StrictStr) -> T:
|
|
|
23
29
|
Raises:
|
|
24
30
|
ValueError: If variable is missing or fails validation
|
|
25
31
|
"""
|
|
32
|
+
vprint(1, f"Attempting to get environment variable: {name}")
|
|
33
|
+
vprint(2, f"Current environment variables: {list(os.environ.keys())}")
|
|
26
34
|
try:
|
|
27
35
|
value = os.environ[name]
|
|
28
36
|
_processed_vars.add(name)
|