GJDutils 0.2.2__tar.gz → 0.3.1__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.1}/.gitignore +1 -0
  2. gjdutils-0.3.1/PKG-INFO +279 -0
  3. gjdutils-0.3.1/README.md +209 -0
  4. gjdutils-0.3.1/__VERSION__.py +1 -0
  5. {gjdutils-0.2.2 → gjdutils-0.3.1}/pyproject.toml +9 -5
  6. gjdutils-0.3.1/scripts/check_git_clean.py +38 -0
  7. {gjdutils-0.2.2 → gjdutils-0.3.1}/scripts/deploy_all.py +0 -4
  8. {gjdutils-0.2.2 → gjdutils-0.3.1}/scripts/deploy_pypiprod.py +9 -1
  9. {gjdutils-0.2.2 → gjdutils-0.3.1}/scripts/deploy_pypitest.py +3 -1
  10. gjdutils-0.3.1/scripts/export_all.sh +25 -0
  11. gjdutils-0.3.1/scripts/install_all_dev_dependencies.py +6 -0
  12. gjdutils-0.2.2/src/gjdutils/cacheing.py → gjdutils-0.3.1/src/gjdutils/caching.py +5 -2
  13. gjdutils-0.2.2/src/gjdutils/collections.py → gjdutils-0.3.1/src/gjdutils/collection_utils.py +11 -5
  14. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/dsci.py +13 -5
  15. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/env.py +9 -1
  16. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/files.py +2 -0
  17. gjdutils-0.3.1/src/gjdutils/functions.py +38 -0
  18. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/google_translate.py +8 -11
  19. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/hashing.py +2 -2
  20. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/html.py +6 -7
  21. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/llms_claude.py +2 -3
  22. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/llms_openai.py +3 -5
  23. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/outloud_text_to_speech.py +10 -8
  24. gjdutils-0.3.1/src/gjdutils/print_utils.py +23 -0
  25. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/pypi_build.py +8 -0
  26. gjdutils-0.3.1/src/gjdutils/pytest_utils.py +24 -0
  27. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/rand.py +3 -3
  28. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/runtime.py +3 -0
  29. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/shell.py +0 -1
  30. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/strings.py +9 -2
  31. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/typ.py +3 -2
  32. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/voice_speechrecognition.py +10 -4
  33. gjdutils-0.3.1/tests/test_env_integration.py +60 -0
  34. gjdutils-0.3.1/tests/test_git_status.py +75 -0
  35. gjdutils-0.3.1/tests/test_print_utils.py +34 -0
  36. gjdutils-0.2.2/PKG-INFO +0 -101
  37. gjdutils-0.2.2/README.md +0 -37
  38. gjdutils-0.2.2/__VERSION__.py +0 -1
  39. gjdutils-0.2.2/src/gjdutils/functions.py +0 -6
  40. gjdutils-0.2.2/src/gjdutils/pytest_utils.py +0 -24
  41. {gjdutils-0.2.2 → gjdutils-0.3.1}/LICENSE +0 -0
  42. {gjdutils-0.2.2 → gjdutils-0.3.1}/docs/CODING_SHELL_SCRIPTS.md +0 -0
  43. {gjdutils-0.2.2 → gjdutils-0.3.1}/planning/250215_publishing_to_pypi.md +0 -0
  44. {gjdutils-0.2.2 → gjdutils-0.3.1}/planning/250215_rename_gdutils_to_gjdutils.md +0 -0
  45. {gjdutils-0.2.2 → gjdutils-0.3.1}/scripts/check_locally.py +0 -0
  46. {gjdutils-0.2.2 → gjdutils-0.3.1}/scripts/check_pypiprod.py +0 -0
  47. {gjdutils-0.2.2 → gjdutils-0.3.1}/scripts/check_pypitest.py +0 -0
  48. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/__init__.py +0 -0
  49. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/audios.py +0 -0
  50. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/cmd.py +0 -0
  51. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/colab.py +0 -0
  52. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/decorators.py +0 -0
  53. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/dicts.py +0 -0
  54. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/dt.py +0 -0
  55. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/errors.py +0 -0
  56. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/indexing.py +0 -0
  57. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/iterfunc.py +0 -0
  58. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/jsons.py +0 -0
  59. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/lists.py +0 -0
  60. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/llm_utils.py +0 -0
  61. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/misc.py +0 -0
  62. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/num.py +0 -0
  63. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/obsolete/google_text_to_speech.py +0 -0
  64. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/obsolete/llms_obsolete.py +0 -0
  65. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/prompt_templates.py +0 -0
  66. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/regex.py +0 -0
  67. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/requirements_dev.txt +0 -0
  68. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/sets.py +0 -0
  69. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/sorteddict.py +0 -0
  70. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/stopwatch.py +0 -0
  71. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/todo/convert_parquet.py +0 -0
  72. {gjdutils-0.2.2 → gjdutils-0.3.1}/src/gjdutils/web.py +0 -0
  73. {gjdutils-0.2.2 → gjdutils-0.3.1}/tests/__init__.py +0 -0
  74. {gjdutils-0.2.2 → gjdutils-0.3.1}/tests/test_indexing.py +0 -0
  75. {gjdutils-0.2.2 → gjdutils-0.3.1}/tests/test_rand.py +0 -0
  76. {gjdutils-0.2.2 → gjdutils-0.3.1}/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,279 @@
1
+ Metadata-Version: 2.4
2
+ Name: GJDutils
3
+ Version: 0.3.1
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: anthropic; extra == 'all-no-dev'
23
+ Requires-Dist: azure-cognitiveservices-speech; extra == 'all-no-dev'
24
+ Requires-Dist: bs4; 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: lxml; extra == 'all-no-dev'
30
+ Requires-Dist: openai; extra == 'all-no-dev'
31
+ Requires-Dist: openai-whisper; extra == 'all-no-dev'
32
+ Requires-Dist: pendulum; extra == 'all-no-dev'
33
+ Requires-Dist: pillow; extra == 'all-no-dev'
34
+ Requires-Dist: playsound; extra == 'all-no-dev'
35
+ Requires-Dist: pyaudio; extra == 'all-no-dev'
36
+ Requires-Dist: pygame; extra == 'all-no-dev'
37
+ Requires-Dist: python-vlc; extra == 'all-no-dev'
38
+ Requires-Dist: speechrecognition; extra == 'all-no-dev'
39
+ Provides-Extra: audio-lang
40
+ Requires-Dist: azure-cognitiveservices-speech; 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: flake8; extra == 'dev'
54
+ Requires-Dist: pytest; extra == 'dev'
55
+ Requires-Dist: rich; extra == 'dev'
56
+ Requires-Dist: twine; extra == 'dev'
57
+ Requires-Dist: typer; extra == 'dev'
58
+ Requires-Dist: wheel; extra == 'dev'
59
+ Provides-Extra: dt
60
+ Requires-Dist: humanize; extra == 'dt'
61
+ Requires-Dist: pendulum; extra == 'dt'
62
+ Provides-Extra: html-web
63
+ Requires-Dist: bs4; extra == 'html-web'
64
+ Requires-Dist: lxml; extra == 'html-web'
65
+ Provides-Extra: llm
66
+ Requires-Dist: anthropic; extra == 'llm'
67
+ Requires-Dist: openai; extra == 'llm'
68
+ Requires-Dist: pillow; extra == 'llm'
69
+ Description-Content-Type: text/markdown
70
+
71
+ # gjdutils
72
+
73
+ A collection of useful utility functions (strings, dates, data science/AI, web development, types, etc).
74
+
75
+ 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.
76
+
77
+ 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.
78
+
79
+ Caveat emptor: some of these are old, and haven't been tested in a while.
80
+
81
+
82
+ ## Highlights
83
+
84
+ ### Audio: convenient microphone voice recognition with Whisper, and text-to-speech using ElevenLabs
85
+ ```python
86
+ from gjdutils.outloud_text_to_speech import outloud
87
+ from gjdutils.voice_speechrecognition import recognise_speech
88
+
89
+ # Record speech and play it back in a different voice
90
+ text = recognise_speech("Say something!") # Records from microphone until you press ENTER
91
+ outloud(text, prog="elevenlabs", mp3_filen="recording.mp3", should_play=True) # Plays back what you said
92
+ ```
93
+
94
+
95
+ ### Run shell commands clearly & conveniently
96
+ ```python
97
+ from gjdutils.cmd import run_cmd
98
+ from pathlib import Path
99
+
100
+ # Get Python version and capture the output
101
+ retcode, stdout, extra = run_cmd(
102
+ "python --version", # you can also provide as a list-of-strings
103
+ before_msg="Checking Python version...",
104
+ fatal_msg="Some problem running Python", # will show up in red, sys.exit(1)
105
+ verbose=0, # Run silently unless there's an error
106
+ **{"timeout": 5} # Pass additional arguments to subprocess
107
+ )
108
+ print(f"Python version: {stdout}") # e.g. "Python 3.9.7"
109
+ print(f"Ran command: {extra['cmd_str']}") # plus lots of other stuff stored
110
+ ```
111
+
112
+
113
+ ### Environment variables with type validation and helpful error messages
114
+ ```bash
115
+ $ source scripts/export_all.sh .env
116
+ ```
117
+
118
+ ```python
119
+ from gjdutils.env import get_env_var
120
+
121
+ api_key = get_env_var("OPENAI_API_KEY") # Ensures non-empty by default
122
+ num_workers = get_env_var("NUM_WORKERS", typ=int) # Validates and converts to int
123
+ ```
124
+
125
+ ### Strict Jinja templating that catches both undefined and unused variables
126
+ ```python
127
+ from gjdutils.strings import jinja_render
128
+
129
+ template = "{{name}} is {{age}} years old"
130
+ context = {"name": "Bob", "unused": True}
131
+ text = jinja_render(template, context) # will fail both because `age` is missing and `unused` is superfluous
132
+ ```
133
+
134
+
135
+ ### Set random seeds across Python, NumPy, PyTorch for reproducibility
136
+ ```python
137
+ from gjdutils.rand import set_seeds
138
+
139
+ set_seeds(42) # Sets seeds for random, numpy, torch if available
140
+ ```
141
+
142
+
143
+ ### Call Claude/OpenAI APIs with function calling, image analysis & JSON support
144
+ ```python
145
+ from gjdutils.llms_claude import call_claude_gpt
146
+ from gjdutils.llm_utils import image_to_base64
147
+
148
+ response, extra = call_claude_gpt(
149
+ "What's in this image?",
150
+ image_filens=["path/to/image.jpg"],
151
+ temperature=0.001
152
+ )
153
+ ```
154
+
155
+
156
+ ### Translate text between languages with Google Translate
157
+ ```python
158
+ from gjdutils.google_translate import translate_text, detect_language
159
+
160
+ # First detect the language
161
+ text = "Bonjour le monde"
162
+ lang, confidence = detect_language(text) # Returns ("fr", 0.98)
163
+
164
+ # Then translate to English
165
+ english_text, _ = translate_text(text, lang_src_code=lang, lang_tgt_code="en") # Returns "Hello world"
166
+ ```
167
+
168
+
169
+ ### Calculate text similarity using longest common substring analysis
170
+ ```python
171
+ from gjdutils.strings import calc_proportion_longest_common_substring
172
+ similarity = calc_proportion_longest_common_substring(["hello world", "hello there"]) # Returns ~0.45 for "hello" match
173
+ ```
174
+
175
+
176
+ ### Measure data uniformity & distribution with simple proportion analysis
177
+ ```python
178
+ from gjdutils.dsci import calc_proportion_identical
179
+ uniformity = calc_proportion_identical(['a', 'a', 'a', 'b']) # Returns 0.75 (75% are 'a')
180
+ ```
181
+
182
+
183
+ ### Generate deterministic cache keys for complex Python objects
184
+ ```python
185
+ from gjdutils.caching import generate_mckey
186
+ cache_key = generate_mckey("myprefix", {"a": 100, "b": "foo"}) # Creates deterministic cache key
187
+ ```
188
+
189
+
190
+ ### Generate consistent hashes for caching/comparison
191
+ ```python
192
+ from gjdutils.hashing import hash_readable
193
+
194
+ # Same input always produces same hash, even across sessions
195
+ config = {"foo": "bar"}
196
+ cache_key = hash_readable(config) # e.g. "8f4e5d3..."
197
+ ```
198
+
199
+
200
+ ### Pretty-print and process HTML with customizable indentation
201
+ ```python
202
+ from gjdutils.html import prettify_html
203
+
204
+ # Prettify a string of HTML (also useful for testing two HTML strings are identical without caring about whitespace)
205
+ html = "<div><p>Hello</p><p>World</p></div>" # Also works with BeautifulSoup elements
206
+ pretty = prettify_html(html, indent=4) # Custom indentation
207
+ print(pretty)
208
+ # <div>
209
+ # <p>Hello</p>
210
+ # <p>World</p>
211
+ # </div>
212
+ ```
213
+
214
+
215
+ ### Debug by printing local variables, excluding noise
216
+ ```python
217
+ from gjdutils.misc import print_locals
218
+
219
+ def my_function(x, y):
220
+ z = x + y
221
+ some_func = lambda x: x * 2
222
+ _internal = "temp"
223
+ # Print all local vars except functions and _prefixed
224
+ print_locals(locals(), ignore_functions=True, ignore_underscores=True)
225
+ # Output: {'x': 1, 'y': 2, 'z': 3}
226
+ ```
227
+
228
+
229
+ ### Generate readable random IDs (no confusing characters)
230
+ ```python
231
+ from gjdutils.rand import gen_readable_rand_id
232
+
233
+ # Generate random ID without confusing chars (0/O, 1/I/l, etc)
234
+ uid = gen_readable_rand_id(n=7) # e.g. "k8m5p3h"
235
+ ```
236
+
237
+ ----
238
+
239
+ ## Installation
240
+
241
+ ```bash
242
+ pip install gjdutils
243
+ ```
244
+
245
+ For optional features:
246
+ ```bash
247
+ pip install "gjdutils[dt]" # Date/time utilities
248
+ pip install "gjdutils[llm]" # AI/LLM integrations
249
+ pip install "gjdutils[audio_lang]" # Speech/translation, language-related
250
+ pip install "gjdutils[html_web]" # Web scraping
251
+
252
+ pip install "gjdutils[dev]" # Development tools (for tweaking `gjdutils` itself, e.g. pytest)
253
+
254
+ # Install all optional dependencies at once (except `dev`, which is used for developing `gjdutils` itself)
255
+ pip install "gjdutils[all_no_dev]"
256
+ ```
257
+
258
+ ### Development Setup
259
+
260
+ If you're developing `gjdutils` itself, install in editable mode:
261
+ ```bash
262
+ # (Assumes you have already setup your virtualenv)
263
+ # from the gjdutils root directory
264
+ pip install -e ".[all_no_dev, dev]" # Install all optional dependencies
265
+ ```
266
+
267
+ Or if you're feeling lazy and can't remember that command, just use:
268
+
269
+ ```bash
270
+ python scripts/install_all_dev_dependencies.py
271
+ ```
272
+
273
+
274
+ ### Adding to requirements.txt
275
+
276
+ To add to your `requirements.txt` in editable mode, e.g. to install all optional dependencies:
277
+ ```text
278
+ -e "git+https://github.com/gregdetre/gjdutils.git#egg=gjdutils[all_no_dev]"
279
+ ```
@@ -0,0 +1,209 @@
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
+ Caveat emptor: some of these are old, and haven't been tested in a while.
10
+
11
+
12
+ ## Highlights
13
+
14
+ ### Audio: convenient microphone voice recognition with Whisper, and text-to-speech using ElevenLabs
15
+ ```python
16
+ from gjdutils.outloud_text_to_speech import outloud
17
+ from gjdutils.voice_speechrecognition import recognise_speech
18
+
19
+ # Record speech and play it back in a different voice
20
+ text = recognise_speech("Say something!") # Records from microphone until you press ENTER
21
+ outloud(text, prog="elevenlabs", mp3_filen="recording.mp3", should_play=True) # Plays back what you said
22
+ ```
23
+
24
+
25
+ ### Run shell commands clearly & conveniently
26
+ ```python
27
+ from gjdutils.cmd import run_cmd
28
+ from pathlib import Path
29
+
30
+ # Get Python version and capture the output
31
+ retcode, stdout, extra = run_cmd(
32
+ "python --version", # you can also provide as a list-of-strings
33
+ before_msg="Checking Python version...",
34
+ fatal_msg="Some problem running Python", # will show up in red, sys.exit(1)
35
+ verbose=0, # Run silently unless there's an error
36
+ **{"timeout": 5} # Pass additional arguments to subprocess
37
+ )
38
+ print(f"Python version: {stdout}") # e.g. "Python 3.9.7"
39
+ print(f"Ran command: {extra['cmd_str']}") # plus lots of other stuff stored
40
+ ```
41
+
42
+
43
+ ### Environment variables with type validation and helpful error messages
44
+ ```bash
45
+ $ source scripts/export_all.sh .env
46
+ ```
47
+
48
+ ```python
49
+ from gjdutils.env import get_env_var
50
+
51
+ api_key = get_env_var("OPENAI_API_KEY") # Ensures non-empty by default
52
+ num_workers = get_env_var("NUM_WORKERS", typ=int) # Validates and converts to int
53
+ ```
54
+
55
+ ### Strict Jinja templating that catches both undefined and unused variables
56
+ ```python
57
+ from gjdutils.strings import jinja_render
58
+
59
+ template = "{{name}} is {{age}} years old"
60
+ context = {"name": "Bob", "unused": True}
61
+ text = jinja_render(template, context) # will fail both because `age` is missing and `unused` is superfluous
62
+ ```
63
+
64
+
65
+ ### Set random seeds across Python, NumPy, PyTorch for reproducibility
66
+ ```python
67
+ from gjdutils.rand import set_seeds
68
+
69
+ set_seeds(42) # Sets seeds for random, numpy, torch if available
70
+ ```
71
+
72
+
73
+ ### Call Claude/OpenAI APIs with function calling, image analysis & JSON support
74
+ ```python
75
+ from gjdutils.llms_claude import call_claude_gpt
76
+ from gjdutils.llm_utils import image_to_base64
77
+
78
+ response, extra = call_claude_gpt(
79
+ "What's in this image?",
80
+ image_filens=["path/to/image.jpg"],
81
+ temperature=0.001
82
+ )
83
+ ```
84
+
85
+
86
+ ### Translate text between languages with Google Translate
87
+ ```python
88
+ from gjdutils.google_translate import translate_text, detect_language
89
+
90
+ # First detect the language
91
+ text = "Bonjour le monde"
92
+ lang, confidence = detect_language(text) # Returns ("fr", 0.98)
93
+
94
+ # Then translate to English
95
+ english_text, _ = translate_text(text, lang_src_code=lang, lang_tgt_code="en") # Returns "Hello world"
96
+ ```
97
+
98
+
99
+ ### Calculate text similarity using longest common substring analysis
100
+ ```python
101
+ from gjdutils.strings import calc_proportion_longest_common_substring
102
+ similarity = calc_proportion_longest_common_substring(["hello world", "hello there"]) # Returns ~0.45 for "hello" match
103
+ ```
104
+
105
+
106
+ ### Measure data uniformity & distribution with simple proportion analysis
107
+ ```python
108
+ from gjdutils.dsci import calc_proportion_identical
109
+ uniformity = calc_proportion_identical(['a', 'a', 'a', 'b']) # Returns 0.75 (75% are 'a')
110
+ ```
111
+
112
+
113
+ ### Generate deterministic cache keys for complex Python objects
114
+ ```python
115
+ from gjdutils.caching import generate_mckey
116
+ cache_key = generate_mckey("myprefix", {"a": 100, "b": "foo"}) # Creates deterministic cache key
117
+ ```
118
+
119
+
120
+ ### Generate consistent hashes for caching/comparison
121
+ ```python
122
+ from gjdutils.hashing import hash_readable
123
+
124
+ # Same input always produces same hash, even across sessions
125
+ config = {"foo": "bar"}
126
+ cache_key = hash_readable(config) # e.g. "8f4e5d3..."
127
+ ```
128
+
129
+
130
+ ### Pretty-print and process HTML with customizable indentation
131
+ ```python
132
+ from gjdutils.html import prettify_html
133
+
134
+ # Prettify a string of HTML (also useful for testing two HTML strings are identical without caring about whitespace)
135
+ html = "<div><p>Hello</p><p>World</p></div>" # Also works with BeautifulSoup elements
136
+ pretty = prettify_html(html, indent=4) # Custom indentation
137
+ print(pretty)
138
+ # <div>
139
+ # <p>Hello</p>
140
+ # <p>World</p>
141
+ # </div>
142
+ ```
143
+
144
+
145
+ ### Debug by printing local variables, excluding noise
146
+ ```python
147
+ from gjdutils.misc import print_locals
148
+
149
+ def my_function(x, y):
150
+ z = x + y
151
+ some_func = lambda x: x * 2
152
+ _internal = "temp"
153
+ # Print all local vars except functions and _prefixed
154
+ print_locals(locals(), ignore_functions=True, ignore_underscores=True)
155
+ # Output: {'x': 1, 'y': 2, 'z': 3}
156
+ ```
157
+
158
+
159
+ ### Generate readable random IDs (no confusing characters)
160
+ ```python
161
+ from gjdutils.rand import gen_readable_rand_id
162
+
163
+ # Generate random ID without confusing chars (0/O, 1/I/l, etc)
164
+ uid = gen_readable_rand_id(n=7) # e.g. "k8m5p3h"
165
+ ```
166
+
167
+ ----
168
+
169
+ ## Installation
170
+
171
+ ```bash
172
+ pip install gjdutils
173
+ ```
174
+
175
+ For optional features:
176
+ ```bash
177
+ pip install "gjdutils[dt]" # Date/time utilities
178
+ pip install "gjdutils[llm]" # AI/LLM integrations
179
+ pip install "gjdutils[audio_lang]" # Speech/translation, language-related
180
+ pip install "gjdutils[html_web]" # Web scraping
181
+
182
+ pip install "gjdutils[dev]" # Development tools (for tweaking `gjdutils` itself, e.g. pytest)
183
+
184
+ # Install all optional dependencies at once (except `dev`, which is used for developing `gjdutils` itself)
185
+ pip install "gjdutils[all_no_dev]"
186
+ ```
187
+
188
+ ### Development Setup
189
+
190
+ If you're developing `gjdutils` itself, install in editable mode:
191
+ ```bash
192
+ # (Assumes you have already setup your virtualenv)
193
+ # from the gjdutils root directory
194
+ pip install -e ".[all_no_dev, dev]" # Install all optional dependencies
195
+ ```
196
+
197
+ Or if you're feeling lazy and can't remember that command, just use:
198
+
199
+ ```bash
200
+ python scripts/install_all_dev_dependencies.py
201
+ ```
202
+
203
+
204
+ ### Adding to requirements.txt
205
+
206
+ To add to your `requirements.txt` in editable mode, e.g. to install all optional dependencies:
207
+ ```text
208
+ -e "git+https://github.com/gregdetre/gjdutils.git#egg=gjdutils[all_no_dev]"
209
+ ```
@@ -0,0 +1 @@
1
+ __version__ = "0.3.1"
@@ -31,26 +31,29 @@ dependencies = [
31
31
  [project.optional-dependencies]
32
32
  audio_lang = [
33
33
  "azure-cognitiveservices-speech",
34
- "cachetools",
35
34
  "google-cloud-texttospeech",
36
35
  "google-cloud-translate",
37
36
  "elevenlabs",
38
37
  # for playing mp3
39
38
  "playsound",
39
+ # for voice_speechrecognition Microphone
40
+ "pyaudio",
40
41
  # for playing mp3
41
42
  "pygame",
42
43
  # for playing mp3
43
44
  "python-vlc",
44
45
  "SpeechRecognition",
46
+ "openai-whisper", # for OpenAI Whisper speech recognition
45
47
  ]
46
48
  dev = [
47
49
  "black",
48
- "pytest",
49
50
  "build",
50
- "twine",
51
- "wheel",
51
+ "flake8",
52
+ "pytest",
52
53
  "rich", # for console output formatting
54
+ "twine",
53
55
  "typer", # for CLI tools
56
+ "wheel",
54
57
  ]
55
58
  dt = [
56
59
  "humanize",
@@ -58,9 +61,10 @@ dt = [
58
61
  ]
59
62
  html_web = [
60
63
  "bs4",
64
+ "lxml",
61
65
  ]
62
66
  llm = [
63
- "cachetools",
67
+ "anthropic",
64
68
  # "llm", no longer using this
65
69
  "openai",
66
70
  "pillow", # for images
@@ -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()
@@ -4,9 +4,7 @@ from rich.console import Console
4
4
 
5
5
  from check_locally import main as check_locally_main
6
6
  from deploy_pypitest import main as deploy_pypitest_main
7
- from check_pypitest import main as check_pypitest_main
8
7
  from deploy_pypiprod import main as deploy_pypiprod_main
9
- from check_pypiprod import main as check_pypiprod_main
10
8
 
11
9
  console = Console()
12
10
 
@@ -16,9 +14,7 @@ def main():
16
14
 
17
15
  check_locally_main()
18
16
  deploy_pypitest_main()
19
- check_pypitest_main()
20
17
  deploy_pypiprod_main()
21
- check_pypiprod_main()
22
18
 
23
19
  console.print("\n[green]Full deployment process completed successfully! 🎉[/green]")
24
20
 
@@ -13,6 +13,10 @@ 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
18
+ from scripts.check_pypiprod import main as check_pypiprod_main
19
+
16
20
 
17
21
  console = Console()
18
22
 
@@ -20,6 +24,9 @@ console = Console()
20
24
  def main():
21
25
  console.rule("[yellow]Starting Production PyPI Deployment")
22
26
 
27
+ # Check git status first
28
+ check_git_status()
29
+
23
30
  # Check if version already exists
24
31
  if check_version_exists(Version(__version__), pypi_env="prod"):
25
32
  fatal_error_msg(
@@ -40,7 +47,8 @@ def main():
40
47
  upload_to_pypi(pypi_env="prod")
41
48
 
42
49
  console.print("\n[green]Deployment to Production PyPI completed![/green]")
43
- console.print("Run ./scripts/check_pypiprod.py to verify the deployment")
50
+
51
+ check_pypiprod_main()
44
52
 
45
53
 
46
54
  if __name__ == "__main__":
@@ -12,6 +12,7 @@ from gjdutils.pypi_build import (
12
12
  build_package,
13
13
  upload_to_pypi,
14
14
  )
15
+ from scripts.check_pypitest import main as check_pypitest_main
15
16
  from gjdutils.shell import fatal_error_msg
16
17
 
17
18
  console = Console()
@@ -32,7 +33,8 @@ def main():
32
33
  upload_to_pypi(pypi_env="test")
33
34
 
34
35
  console.print("\n[green]Deployment to Test PyPI completed![/green]")
35
- console.print("Run ./scripts/check_pypitest.py to verify the deployment")
36
+
37
+ check_pypitest_main()
36
38
 
37
39
 
38
40
  if __name__ == "__main__":
@@ -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)