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.
Files changed (76) hide show
  1. {gjdutils-0.2.2 → gjdutils-0.3.0}/.gitignore +1 -0
  2. gjdutils-0.3.0/PKG-INFO +273 -0
  3. gjdutils-0.3.0/README.md +205 -0
  4. gjdutils-0.3.0/__VERSION__.py +1 -0
  5. {gjdutils-0.2.2 → gjdutils-0.3.0}/pyproject.toml +3 -0
  6. gjdutils-0.3.0/scripts/check_git_clean.py +38 -0
  7. {gjdutils-0.2.2 → gjdutils-0.3.0}/scripts/deploy_pypiprod.py +5 -0
  8. gjdutils-0.3.0/scripts/export_all.sh +25 -0
  9. gjdutils-0.3.0/scripts/install_all_dev_dependencies.py +6 -0
  10. gjdutils-0.2.2/src/gjdutils/cacheing.py → gjdutils-0.3.0/src/gjdutils/caching.py +5 -2
  11. gjdutils-0.2.2/src/gjdutils/collections.py → gjdutils-0.3.0/src/gjdutils/collection_utils.py +11 -5
  12. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/env.py +9 -1
  13. gjdutils-0.3.0/src/gjdutils/functions.py +38 -0
  14. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/google_translate.py +8 -11
  15. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/hashing.py +2 -2
  16. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/pypi_build.py +8 -0
  17. gjdutils-0.3.0/src/gjdutils/pytest_utils.py +24 -0
  18. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/rand.py +3 -3
  19. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/runtime.py +3 -0
  20. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/voice_speechrecognition.py +10 -4
  21. gjdutils-0.3.0/src/gjdutils/vprint.py +20 -0
  22. gjdutils-0.3.0/tests/test_env_integration.py +60 -0
  23. gjdutils-0.3.0/tests/test_git_status.py +75 -0
  24. gjdutils-0.3.0/tests/test_vprint.py +34 -0
  25. gjdutils-0.2.2/PKG-INFO +0 -101
  26. gjdutils-0.2.2/README.md +0 -37
  27. gjdutils-0.2.2/__VERSION__.py +0 -1
  28. gjdutils-0.2.2/src/gjdutils/functions.py +0 -6
  29. gjdutils-0.2.2/src/gjdutils/pytest_utils.py +0 -24
  30. {gjdutils-0.2.2 → gjdutils-0.3.0}/LICENSE +0 -0
  31. {gjdutils-0.2.2 → gjdutils-0.3.0}/docs/CODING_SHELL_SCRIPTS.md +0 -0
  32. {gjdutils-0.2.2 → gjdutils-0.3.0}/planning/250215_publishing_to_pypi.md +0 -0
  33. {gjdutils-0.2.2 → gjdutils-0.3.0}/planning/250215_rename_gdutils_to_gjdutils.md +0 -0
  34. {gjdutils-0.2.2 → gjdutils-0.3.0}/scripts/check_locally.py +0 -0
  35. {gjdutils-0.2.2 → gjdutils-0.3.0}/scripts/check_pypiprod.py +0 -0
  36. {gjdutils-0.2.2 → gjdutils-0.3.0}/scripts/check_pypitest.py +0 -0
  37. {gjdutils-0.2.2 → gjdutils-0.3.0}/scripts/deploy_all.py +0 -0
  38. {gjdutils-0.2.2 → gjdutils-0.3.0}/scripts/deploy_pypitest.py +0 -0
  39. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/__init__.py +0 -0
  40. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/audios.py +0 -0
  41. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/cmd.py +0 -0
  42. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/colab.py +0 -0
  43. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/decorators.py +0 -0
  44. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/dicts.py +0 -0
  45. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/dsci.py +0 -0
  46. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/dt.py +0 -0
  47. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/errors.py +0 -0
  48. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/files.py +0 -0
  49. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/html.py +0 -0
  50. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/indexing.py +0 -0
  51. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/iterfunc.py +0 -0
  52. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/jsons.py +0 -0
  53. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/lists.py +0 -0
  54. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/llm_utils.py +0 -0
  55. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/llms_claude.py +0 -0
  56. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/llms_openai.py +0 -0
  57. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/misc.py +0 -0
  58. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/num.py +0 -0
  59. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/obsolete/google_text_to_speech.py +0 -0
  60. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/obsolete/llms_obsolete.py +0 -0
  61. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/outloud_text_to_speech.py +0 -0
  62. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/prompt_templates.py +0 -0
  63. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/regex.py +0 -0
  64. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/requirements_dev.txt +0 -0
  65. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/sets.py +0 -0
  66. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/shell.py +0 -0
  67. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/sorteddict.py +0 -0
  68. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/stopwatch.py +0 -0
  69. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/strings.py +0 -0
  70. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/todo/convert_parquet.py +0 -0
  71. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/typ.py +0 -0
  72. {gjdutils-0.2.2 → gjdutils-0.3.0}/src/gjdutils/web.py +0 -0
  73. {gjdutils-0.2.2 → gjdutils-0.3.0}/tests/__init__.py +0 -0
  74. {gjdutils-0.2.2 → gjdutils-0.3.0}/tests/test_indexing.py +0 -0
  75. {gjdutils-0.2.2 → gjdutils-0.3.0}/tests/test_rand.py +0 -0
  76. {gjdutils-0.2.2 → gjdutils-0.3.0}/tests/test_strings.py +0 -0
@@ -88,6 +88,7 @@ celerybeat-schedule
88
88
 
89
89
  # Environments
90
90
  .env
91
+ .env.*
91
92
  .venv
92
93
  env/
93
94
  venv/
@@ -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
+ ```
@@ -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)
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from gjdutils.cmd import run_cmd
4
+
5
+ if __name__ == "__main__":
6
+ run_cmd("pip install -e '.[all_no_dev,dev]'", verbose=4)
@@ -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 python dict's keys() is deterministic, but by
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
@@ -1,10 +1,18 @@
1
- from collections import OrderedDict
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
- def get_env_var(name: str, typ: Any = StrictStr) -> T:
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)