GJDutils 0.3.0__tar.gz → 0.3.4__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.3.0 → gjdutils-0.3.4}/PKG-INFO +22 -11
- {gjdutils-0.3.0 → gjdutils-0.3.4}/README.md +11 -7
- {gjdutils-0.3.0 → gjdutils-0.3.4}/planning/250215_publishing_to_pypi.md +2 -2
- {gjdutils-0.3.0 → gjdutils-0.3.4}/pyproject.toml +12 -10
- {gjdutils-0.3.0 → gjdutils-0.3.4}/scripts/check_locally.py +3 -1
- {gjdutils-0.3.0 → gjdutils-0.3.4}/scripts/check_pypiprod.py +4 -2
- {gjdutils-0.3.0 → gjdutils-0.3.4}/scripts/check_pypitest.py +4 -2
- {gjdutils-0.3.0 → gjdutils-0.3.4}/scripts/deploy_all.py +3 -7
- {gjdutils-0.3.0 → gjdutils-0.3.4}/scripts/deploy_pypiprod.py +6 -6
- {gjdutils-0.3.0 → gjdutils-0.3.4}/scripts/deploy_pypitest.py +4 -3
- gjdutils-0.3.4/src/gjdutils/__init__.py +7 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/caching.py +1 -2
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/dsci.py +13 -5
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/env.py +2 -2
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/files.py +2 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/functions.py +6 -6
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/html.py +6 -7
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/jsons.py +7 -3
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/llm_utils.py +3 -3
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/llms_claude.py +2 -3
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/llms_openai.py +3 -5
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/num.py +5 -5
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/outloud_text_to_speech.py +10 -8
- gjdutils-0.3.0/src/gjdutils/vprint.py → gjdutils-0.3.4/src/gjdutils/print_utils.py +5 -2
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/pypi_build.py +25 -10
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/runtime.py +1 -1
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/shell.py +0 -1
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/strings.py +9 -2
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/typ.py +3 -2
- gjdutils-0.3.4/tests/test_num.py +18 -0
- gjdutils-0.3.0/tests/test_vprint.py → gjdutils-0.3.4/tests/test_print_utils.py +1 -1
- gjdutils-0.3.0/__VERSION__.py +0 -1
- gjdutils-0.3.0/src/gjdutils/__init__.py +0 -12
- gjdutils-0.3.0/src/gjdutils/sorteddict.py +0 -34
- {gjdutils-0.3.0 → gjdutils-0.3.4}/.gitignore +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/LICENSE +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/docs/CODING_SHELL_SCRIPTS.md +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/planning/250215_rename_gdutils_to_gjdutils.md +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/scripts/check_git_clean.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/scripts/export_all.sh +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/scripts/install_all_dev_dependencies.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/audios.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/cmd.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/colab.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/collection_utils.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/decorators.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/dicts.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/dt.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/errors.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/google_translate.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/hashing.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/indexing.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/iterfunc.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/lists.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/misc.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/obsolete/google_text_to_speech.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/obsolete/llms_obsolete.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/prompt_templates.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/pytest_utils.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/rand.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/regex.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/requirements_dev.txt +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/sets.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/stopwatch.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/todo/convert_parquet.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/voice_speechrecognition.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/src/gjdutils/web.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/tests/__init__.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/tests/test_env_integration.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/tests/test_git_status.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/tests/test_indexing.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/tests/test_rand.py +0 -0
- {gjdutils-0.3.0 → gjdutils-0.3.4}/tests/test_strings.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: GJDutils
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: A collection of useful utility functions (basics, data science/AI, web development, etc)
|
|
5
5
|
Project-URL: Homepage, https://github.com/gregdetre/gjdutils
|
|
6
6
|
Project-URL: Repository, https://github.com/gregdetre/gjdutils
|
|
@@ -19,15 +19,18 @@ Requires-Dist: jinja2
|
|
|
19
19
|
Requires-Dist: pydantic
|
|
20
20
|
Requires-Dist: python-dotenv
|
|
21
21
|
Provides-Extra: all-no-dev
|
|
22
|
+
Requires-Dist: anthropic; extra == 'all-no-dev'
|
|
22
23
|
Requires-Dist: azure-cognitiveservices-speech; extra == 'all-no-dev'
|
|
23
24
|
Requires-Dist: bs4; extra == 'all-no-dev'
|
|
24
|
-
Requires-Dist: cachetools; extra == 'all-no-dev'
|
|
25
25
|
Requires-Dist: elevenlabs; extra == 'all-no-dev'
|
|
26
26
|
Requires-Dist: google-cloud-texttospeech; extra == 'all-no-dev'
|
|
27
27
|
Requires-Dist: google-cloud-translate; extra == 'all-no-dev'
|
|
28
28
|
Requires-Dist: humanize; extra == 'all-no-dev'
|
|
29
|
+
Requires-Dist: lxml; extra == 'all-no-dev'
|
|
30
|
+
Requires-Dist: numpy; extra == 'all-no-dev'
|
|
29
31
|
Requires-Dist: openai; extra == 'all-no-dev'
|
|
30
32
|
Requires-Dist: openai-whisper; extra == 'all-no-dev'
|
|
33
|
+
Requires-Dist: pandas; extra == 'all-no-dev'
|
|
31
34
|
Requires-Dist: pendulum; extra == 'all-no-dev'
|
|
32
35
|
Requires-Dist: pillow; extra == 'all-no-dev'
|
|
33
36
|
Requires-Dist: playsound; extra == 'all-no-dev'
|
|
@@ -37,7 +40,6 @@ Requires-Dist: python-vlc; extra == 'all-no-dev'
|
|
|
37
40
|
Requires-Dist: speechrecognition; extra == 'all-no-dev'
|
|
38
41
|
Provides-Extra: audio-lang
|
|
39
42
|
Requires-Dist: azure-cognitiveservices-speech; extra == 'audio-lang'
|
|
40
|
-
Requires-Dist: cachetools; extra == 'audio-lang'
|
|
41
43
|
Requires-Dist: elevenlabs; extra == 'audio-lang'
|
|
42
44
|
Requires-Dist: google-cloud-texttospeech; extra == 'audio-lang'
|
|
43
45
|
Requires-Dist: google-cloud-translate; extra == 'audio-lang'
|
|
@@ -50,18 +52,23 @@ Requires-Dist: speechrecognition; extra == 'audio-lang'
|
|
|
50
52
|
Provides-Extra: dev
|
|
51
53
|
Requires-Dist: black; extra == 'dev'
|
|
52
54
|
Requires-Dist: build; extra == 'dev'
|
|
55
|
+
Requires-Dist: flake8; extra == 'dev'
|
|
53
56
|
Requires-Dist: pytest; extra == 'dev'
|
|
54
57
|
Requires-Dist: rich; extra == 'dev'
|
|
55
58
|
Requires-Dist: twine; extra == 'dev'
|
|
56
59
|
Requires-Dist: typer; extra == 'dev'
|
|
57
60
|
Requires-Dist: wheel; extra == 'dev'
|
|
61
|
+
Provides-Extra: dsci
|
|
62
|
+
Requires-Dist: numpy; extra == 'dsci'
|
|
63
|
+
Requires-Dist: pandas; extra == 'dsci'
|
|
58
64
|
Provides-Extra: dt
|
|
59
65
|
Requires-Dist: humanize; extra == 'dt'
|
|
60
66
|
Requires-Dist: pendulum; extra == 'dt'
|
|
61
67
|
Provides-Extra: html-web
|
|
62
68
|
Requires-Dist: bs4; extra == 'html-web'
|
|
69
|
+
Requires-Dist: lxml; extra == 'html-web'
|
|
63
70
|
Provides-Extra: llm
|
|
64
|
-
Requires-Dist:
|
|
71
|
+
Requires-Dist: anthropic; extra == 'llm'
|
|
65
72
|
Requires-Dist: openai; extra == 'llm'
|
|
66
73
|
Requires-Dist: pillow; extra == 'llm'
|
|
67
74
|
Description-Content-Type: text/markdown
|
|
@@ -74,6 +81,8 @@ This is a smorgasbord of utility functions, patterns and convenient wrappers tha
|
|
|
74
81
|
|
|
75
82
|
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
83
|
|
|
84
|
+
Caveat emptor: some of these are old, and haven't been tested in a while.
|
|
85
|
+
|
|
77
86
|
|
|
78
87
|
## Highlights
|
|
79
88
|
|
|
@@ -97,7 +106,7 @@ from pathlib import Path
|
|
|
97
106
|
retcode, stdout, extra = run_cmd(
|
|
98
107
|
"python --version", # you can also provide as a list-of-strings
|
|
99
108
|
before_msg="Checking Python version...",
|
|
100
|
-
fatal_msg="Some problem running Python", # will show up in red
|
|
109
|
+
fatal_msg="Some problem running Python", # will show up in red, sys.exit(1)
|
|
101
110
|
verbose=0, # Run silently unless there's an error
|
|
102
111
|
**{"timeout": 5} # Pass additional arguments to subprocess
|
|
103
112
|
)
|
|
@@ -108,7 +117,7 @@ print(f"Ran command: {extra['cmd_str']}") # plus lots of other stuff stored
|
|
|
108
117
|
|
|
109
118
|
### Environment variables with type validation and helpful error messages
|
|
110
119
|
```bash
|
|
111
|
-
$
|
|
120
|
+
$ python -m gjdutils.scripts.export_all .env
|
|
112
121
|
```
|
|
113
122
|
|
|
114
123
|
```python
|
|
@@ -135,6 +144,7 @@ from gjdutils.rand import set_seeds
|
|
|
135
144
|
set_seeds(42) # Sets seeds for random, numpy, torch if available
|
|
136
145
|
```
|
|
137
146
|
|
|
147
|
+
|
|
138
148
|
### Call Claude/OpenAI APIs with function calling, image analysis & JSON support
|
|
139
149
|
```python
|
|
140
150
|
from gjdutils.llms_claude import call_claude_gpt
|
|
@@ -142,7 +152,7 @@ from gjdutils.llm_utils import image_to_base64
|
|
|
142
152
|
|
|
143
153
|
response, extra = call_claude_gpt(
|
|
144
154
|
"What's in this image?",
|
|
145
|
-
image_filens=["path/to/image.jpg"],
|
|
155
|
+
image_filens=["path/to/image.jpg"],
|
|
146
156
|
temperature=0.001
|
|
147
157
|
)
|
|
148
158
|
```
|
|
@@ -175,11 +185,10 @@ uniformity = calc_proportion_identical(['a', 'a', 'a', 'b']) # Returns 0.75 (75
|
|
|
175
185
|
```
|
|
176
186
|
|
|
177
187
|
|
|
178
|
-
|
|
179
188
|
### Generate deterministic cache keys for complex Python objects
|
|
180
189
|
```python
|
|
181
190
|
from gjdutils.caching import generate_mckey
|
|
182
|
-
cache_key = generate_mckey("myprefix", {"
|
|
191
|
+
cache_key = generate_mckey("myprefix", {"a": 100, "b": "foo"}) # Creates deterministic cache key
|
|
183
192
|
```
|
|
184
193
|
|
|
185
194
|
|
|
@@ -188,7 +197,7 @@ cache_key = generate_mckey("myprefix", {"user_id": 123, "action": "login"}) # C
|
|
|
188
197
|
from gjdutils.hashing import hash_readable
|
|
189
198
|
|
|
190
199
|
# Same input always produces same hash, even across sessions
|
|
191
|
-
config = {"
|
|
200
|
+
config = {"foo": "bar"}
|
|
192
201
|
cache_key = hash_readable(config) # e.g. "8f4e5d3..."
|
|
193
202
|
```
|
|
194
203
|
|
|
@@ -207,6 +216,7 @@ print(pretty)
|
|
|
207
216
|
# </div>
|
|
208
217
|
```
|
|
209
218
|
|
|
219
|
+
|
|
210
220
|
### Debug by printing local variables, excluding noise
|
|
211
221
|
```python
|
|
212
222
|
from gjdutils.misc import print_locals
|
|
@@ -220,6 +230,7 @@ def my_function(x, y):
|
|
|
220
230
|
# Output: {'x': 1, 'y': 2, 'z': 3}
|
|
221
231
|
```
|
|
222
232
|
|
|
233
|
+
|
|
223
234
|
### Generate readable random IDs (no confusing characters)
|
|
224
235
|
```python
|
|
225
236
|
from gjdutils.rand import gen_readable_rand_id
|
|
@@ -261,7 +272,7 @@ pip install -e ".[all_no_dev, dev]" # Install all optional dependencies
|
|
|
261
272
|
Or if you're feeling lazy and can't remember that command, just use:
|
|
262
273
|
|
|
263
274
|
```bash
|
|
264
|
-
python scripts
|
|
275
|
+
python -m gjdutils.scripts.install_all_dev_dependencies
|
|
265
276
|
```
|
|
266
277
|
|
|
267
278
|
|
|
@@ -6,6 +6,8 @@ This is a smorgasbord of utility functions, patterns and convenient wrappers tha
|
|
|
6
6
|
|
|
7
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
8
|
|
|
9
|
+
Caveat emptor: some of these are old, and haven't been tested in a while.
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
## Highlights
|
|
11
13
|
|
|
@@ -29,7 +31,7 @@ from pathlib import Path
|
|
|
29
31
|
retcode, stdout, extra = run_cmd(
|
|
30
32
|
"python --version", # you can also provide as a list-of-strings
|
|
31
33
|
before_msg="Checking Python version...",
|
|
32
|
-
fatal_msg="Some problem running Python", # will show up in red
|
|
34
|
+
fatal_msg="Some problem running Python", # will show up in red, sys.exit(1)
|
|
33
35
|
verbose=0, # Run silently unless there's an error
|
|
34
36
|
**{"timeout": 5} # Pass additional arguments to subprocess
|
|
35
37
|
)
|
|
@@ -40,7 +42,7 @@ print(f"Ran command: {extra['cmd_str']}") # plus lots of other stuff stored
|
|
|
40
42
|
|
|
41
43
|
### Environment variables with type validation and helpful error messages
|
|
42
44
|
```bash
|
|
43
|
-
$
|
|
45
|
+
$ python -m gjdutils.scripts.export_all .env
|
|
44
46
|
```
|
|
45
47
|
|
|
46
48
|
```python
|
|
@@ -67,6 +69,7 @@ from gjdutils.rand import set_seeds
|
|
|
67
69
|
set_seeds(42) # Sets seeds for random, numpy, torch if available
|
|
68
70
|
```
|
|
69
71
|
|
|
72
|
+
|
|
70
73
|
### Call Claude/OpenAI APIs with function calling, image analysis & JSON support
|
|
71
74
|
```python
|
|
72
75
|
from gjdutils.llms_claude import call_claude_gpt
|
|
@@ -74,7 +77,7 @@ from gjdutils.llm_utils import image_to_base64
|
|
|
74
77
|
|
|
75
78
|
response, extra = call_claude_gpt(
|
|
76
79
|
"What's in this image?",
|
|
77
|
-
image_filens=["path/to/image.jpg"],
|
|
80
|
+
image_filens=["path/to/image.jpg"],
|
|
78
81
|
temperature=0.001
|
|
79
82
|
)
|
|
80
83
|
```
|
|
@@ -107,11 +110,10 @@ uniformity = calc_proportion_identical(['a', 'a', 'a', 'b']) # Returns 0.75 (75
|
|
|
107
110
|
```
|
|
108
111
|
|
|
109
112
|
|
|
110
|
-
|
|
111
113
|
### Generate deterministic cache keys for complex Python objects
|
|
112
114
|
```python
|
|
113
115
|
from gjdutils.caching import generate_mckey
|
|
114
|
-
cache_key = generate_mckey("myprefix", {"
|
|
116
|
+
cache_key = generate_mckey("myprefix", {"a": 100, "b": "foo"}) # Creates deterministic cache key
|
|
115
117
|
```
|
|
116
118
|
|
|
117
119
|
|
|
@@ -120,7 +122,7 @@ cache_key = generate_mckey("myprefix", {"user_id": 123, "action": "login"}) # C
|
|
|
120
122
|
from gjdutils.hashing import hash_readable
|
|
121
123
|
|
|
122
124
|
# Same input always produces same hash, even across sessions
|
|
123
|
-
config = {"
|
|
125
|
+
config = {"foo": "bar"}
|
|
124
126
|
cache_key = hash_readable(config) # e.g. "8f4e5d3..."
|
|
125
127
|
```
|
|
126
128
|
|
|
@@ -139,6 +141,7 @@ print(pretty)
|
|
|
139
141
|
# </div>
|
|
140
142
|
```
|
|
141
143
|
|
|
144
|
+
|
|
142
145
|
### Debug by printing local variables, excluding noise
|
|
143
146
|
```python
|
|
144
147
|
from gjdutils.misc import print_locals
|
|
@@ -152,6 +155,7 @@ def my_function(x, y):
|
|
|
152
155
|
# Output: {'x': 1, 'y': 2, 'z': 3}
|
|
153
156
|
```
|
|
154
157
|
|
|
158
|
+
|
|
155
159
|
### Generate readable random IDs (no confusing characters)
|
|
156
160
|
```python
|
|
157
161
|
from gjdutils.rand import gen_readable_rand_id
|
|
@@ -193,7 +197,7 @@ pip install -e ".[all_no_dev, dev]" # Install all optional dependencies
|
|
|
193
197
|
Or if you're feeling lazy and can't remember that command, just use:
|
|
194
198
|
|
|
195
199
|
```bash
|
|
196
|
-
python scripts
|
|
200
|
+
python -m gjdutils.scripts.install_all_dev_dependencies
|
|
197
201
|
```
|
|
198
202
|
|
|
199
203
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
1. Build and test package locally:
|
|
17
17
|
```bash
|
|
18
18
|
# Option 1: Automated testing script (recommended)
|
|
19
|
-
|
|
19
|
+
python -m gjdutils.scripts.check_locally
|
|
20
20
|
|
|
21
21
|
# Option 2: Manual steps
|
|
22
22
|
# Clean any existing builds
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
2. Test PyPI Deployment:
|
|
30
30
|
```bash
|
|
31
31
|
# Option 1: Automated testing script (recommended)
|
|
32
|
-
|
|
32
|
+
python -m gjdutils.scripts.check_test_pypi
|
|
33
33
|
|
|
34
34
|
# Option 2: Manual steps
|
|
35
35
|
# Upload to test.pypi.org
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
# case-insensitive
|
|
3
3
|
name = "GJDutils"
|
|
4
|
-
|
|
5
|
-
dynamic = ["version"]
|
|
4
|
+
version = "0.3.4"
|
|
6
5
|
authors = [
|
|
7
6
|
{ name="Greg Detre", email="greg@gregdetre.com" },
|
|
8
7
|
]
|
|
@@ -31,7 +30,6 @@ dependencies = [
|
|
|
31
30
|
[project.optional-dependencies]
|
|
32
31
|
audio_lang = [
|
|
33
32
|
"azure-cognitiveservices-speech",
|
|
34
|
-
"cachetools",
|
|
35
33
|
"google-cloud-texttospeech",
|
|
36
34
|
"google-cloud-translate",
|
|
37
35
|
"elevenlabs",
|
|
@@ -48,12 +46,17 @@ audio_lang = [
|
|
|
48
46
|
]
|
|
49
47
|
dev = [
|
|
50
48
|
"black",
|
|
51
|
-
"pytest",
|
|
52
49
|
"build",
|
|
53
|
-
"
|
|
54
|
-
"
|
|
50
|
+
"flake8",
|
|
51
|
+
"pytest",
|
|
55
52
|
"rich", # for console output formatting
|
|
53
|
+
"twine",
|
|
56
54
|
"typer", # for CLI tools
|
|
55
|
+
"wheel",
|
|
56
|
+
]
|
|
57
|
+
dsci = [
|
|
58
|
+
"numpy",
|
|
59
|
+
"pandas",
|
|
57
60
|
]
|
|
58
61
|
dt = [
|
|
59
62
|
"humanize",
|
|
@@ -61,9 +64,10 @@ dt = [
|
|
|
61
64
|
]
|
|
62
65
|
html_web = [
|
|
63
66
|
"bs4",
|
|
67
|
+
"lxml",
|
|
64
68
|
]
|
|
65
69
|
llm = [
|
|
66
|
-
"
|
|
70
|
+
"anthropic",
|
|
67
71
|
# "llm", no longer using this
|
|
68
72
|
"openai",
|
|
69
73
|
"pillow", # for images
|
|
@@ -71,6 +75,7 @@ llm = [
|
|
|
71
75
|
|
|
72
76
|
all_no_dev = [
|
|
73
77
|
"gjdutils[audio_lang]",
|
|
78
|
+
"gjdutils[dsci]",
|
|
74
79
|
"gjdutils[dt]",
|
|
75
80
|
"gjdutils[html_web]",
|
|
76
81
|
"gjdutils[llm]",
|
|
@@ -94,6 +99,3 @@ exclude = [
|
|
|
94
99
|
"gjdutils/todo/",
|
|
95
100
|
"tests/fixme_*.py",
|
|
96
101
|
]
|
|
97
|
-
|
|
98
|
-
[tool.hatch.version]
|
|
99
|
-
path = "__VERSION__.py"
|
|
@@ -37,6 +37,9 @@ def install_and_test_package(python_path: Path, wheel_file: Path):
|
|
|
37
37
|
fatal_msg="Failed to install package",
|
|
38
38
|
)
|
|
39
39
|
|
|
40
|
+
# Install all optional dependencies first
|
|
41
|
+
check_install_optional_features(python_path)
|
|
42
|
+
|
|
40
43
|
# Command: pip install ".[dev]"
|
|
41
44
|
run_cmd(
|
|
42
45
|
f"{python_path} -m pip install '.[dev]'",
|
|
@@ -65,7 +68,6 @@ def main():
|
|
|
65
68
|
install_and_test_package(python_path, wheel_file)
|
|
66
69
|
verify_installation(python_path)
|
|
67
70
|
run_test_suite(python_path)
|
|
68
|
-
check_install_optional_features(python_path)
|
|
69
71
|
|
|
70
72
|
console.print("\nLocal testing completed successfully!", style="green")
|
|
71
73
|
|
|
@@ -13,12 +13,15 @@ console = Console()
|
|
|
13
13
|
|
|
14
14
|
def install_from_pypiprod(python_path: Path):
|
|
15
15
|
# Command: pip install gjdutils
|
|
16
|
-
|
|
16
|
+
run_cmd(
|
|
17
17
|
f"{python_path} -m pip install gjdutils",
|
|
18
18
|
before_msg="Installing package from PyPI...",
|
|
19
19
|
fatal_msg="Failed to install package from PyPI",
|
|
20
20
|
)
|
|
21
21
|
|
|
22
|
+
# Install all optional dependencies
|
|
23
|
+
check_install_optional_features(python_path, from_test_pypi=False)
|
|
24
|
+
|
|
22
25
|
|
|
23
26
|
def main():
|
|
24
27
|
console.rule("[yellow]Starting Production PyPI package testing")
|
|
@@ -27,7 +30,6 @@ def main():
|
|
|
27
30
|
with temp_venv(venv_path) as python_path:
|
|
28
31
|
install_from_pypiprod(python_path)
|
|
29
32
|
verify_installation(python_path)
|
|
30
|
-
check_install_optional_features(python_path, from_test_pypi=False)
|
|
31
33
|
|
|
32
34
|
console.print("\nProduction PyPI testing completed successfully!", style="green")
|
|
33
35
|
|
|
@@ -13,13 +13,16 @@ console = Console()
|
|
|
13
13
|
|
|
14
14
|
def install_from_test_pypi(python_path: Path):
|
|
15
15
|
# Command: pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ gjdutils
|
|
16
|
-
|
|
16
|
+
run_cmd(
|
|
17
17
|
f"{python_path} -m pip install --index-url https://test.pypi.org/simple/ "
|
|
18
18
|
"--extra-index-url https://pypi.org/simple/ gjdutils",
|
|
19
19
|
before_msg="Installing package from Test PyPI...",
|
|
20
20
|
fatal_msg="Failed to install package from Test PyPI",
|
|
21
21
|
)
|
|
22
22
|
|
|
23
|
+
# Install all optional dependencies
|
|
24
|
+
check_install_optional_features(python_path, from_test_pypi=True)
|
|
25
|
+
|
|
23
26
|
|
|
24
27
|
def main():
|
|
25
28
|
console.rule("[yellow]Starting Test PyPI package testing")
|
|
@@ -28,7 +31,6 @@ def main():
|
|
|
28
31
|
with temp_venv(venv_path) as python_path:
|
|
29
32
|
install_from_test_pypi(python_path)
|
|
30
33
|
verify_installation(python_path)
|
|
31
|
-
check_install_optional_features(python_path, from_test_pypi=True)
|
|
32
34
|
|
|
33
35
|
console.print("\nTest PyPI testing completed successfully!", style="green")
|
|
34
36
|
|
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
from rich.console import Console
|
|
4
4
|
|
|
5
|
-
from check_locally import main as check_locally_main
|
|
6
|
-
from deploy_pypitest import main as deploy_pypitest_main
|
|
7
|
-
from
|
|
8
|
-
from deploy_pypiprod import main as deploy_pypiprod_main
|
|
9
|
-
from check_pypiprod import main as check_pypiprod_main
|
|
5
|
+
from .check_locally import main as check_locally_main
|
|
6
|
+
from .deploy_pypitest import main as deploy_pypitest_main
|
|
7
|
+
from .deploy_pypiprod import main as deploy_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
|
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
|
|
3
3
|
from rich.console import Console
|
|
4
|
-
from pathlib import Path
|
|
5
4
|
from packaging.version import Version
|
|
6
5
|
|
|
7
6
|
from gjdutils import __version__
|
|
8
|
-
from gjdutils.decorators import console_print_doc
|
|
9
7
|
from gjdutils.pypi_build import (
|
|
10
8
|
check_version_exists,
|
|
11
9
|
clean_build_dirs,
|
|
@@ -13,8 +11,9 @@ from gjdutils.pypi_build import (
|
|
|
13
11
|
upload_to_pypi,
|
|
14
12
|
)
|
|
15
13
|
from gjdutils.shell import fatal_error_msg
|
|
16
|
-
from
|
|
17
|
-
from
|
|
14
|
+
from .check_git_clean import check_git_status
|
|
15
|
+
from .check_pypiprod import main as check_pypiprod_main
|
|
16
|
+
|
|
18
17
|
|
|
19
18
|
console = Console()
|
|
20
19
|
|
|
@@ -28,7 +27,7 @@ def main():
|
|
|
28
27
|
# Check if version already exists
|
|
29
28
|
if check_version_exists(Version(__version__), pypi_env="prod"):
|
|
30
29
|
fatal_error_msg(
|
|
31
|
-
f"Version {__version__} already exists on PyPI.\nPlease update
|
|
30
|
+
f"Version {__version__} already exists on PyPI.\nPlease update the version in pyproject.toml to a new version number first."
|
|
32
31
|
)
|
|
33
32
|
|
|
34
33
|
# Confirm with user before proceeding
|
|
@@ -45,7 +44,8 @@ def main():
|
|
|
45
44
|
upload_to_pypi(pypi_env="prod")
|
|
46
45
|
|
|
47
46
|
console.print("\n[green]Deployment to Production PyPI completed![/green]")
|
|
48
|
-
|
|
47
|
+
|
|
48
|
+
check_pypiprod_main()
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
if __name__ == "__main__":
|
|
@@ -5,13 +5,13 @@ from pathlib import Path
|
|
|
5
5
|
from packaging.version import Version
|
|
6
6
|
|
|
7
7
|
from gjdutils import __version__
|
|
8
|
-
from gjdutils.decorators import console_print_doc
|
|
9
8
|
from gjdutils.pypi_build import (
|
|
10
9
|
check_version_exists,
|
|
11
10
|
clean_build_dirs,
|
|
12
11
|
build_package,
|
|
13
12
|
upload_to_pypi,
|
|
14
13
|
)
|
|
14
|
+
from .check_pypitest import main as check_pypitest_main
|
|
15
15
|
from gjdutils.shell import fatal_error_msg
|
|
16
16
|
|
|
17
17
|
console = Console()
|
|
@@ -23,7 +23,7 @@ def main():
|
|
|
23
23
|
# Check if version already exists
|
|
24
24
|
if check_version_exists(Version(__version__), pypi_env="test"):
|
|
25
25
|
fatal_error_msg(
|
|
26
|
-
f"Version {__version__} already exists on Test PyPI.\nPlease update
|
|
26
|
+
f"Version {__version__} already exists on Test PyPI.\nPlease update the version in pyproject.toml to a new version number first."
|
|
27
27
|
)
|
|
28
28
|
|
|
29
29
|
# Execute deployment steps
|
|
@@ -32,7 +32,8 @@ def main():
|
|
|
32
32
|
upload_to_pypi(pypi_env="test")
|
|
33
33
|
|
|
34
34
|
console.print("\n[green]Deployment to Test PyPI completed![/green]")
|
|
35
|
-
|
|
35
|
+
|
|
36
|
+
check_pypitest_main()
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
if __name__ == "__main__":
|
|
@@ -2,7 +2,6 @@ from functools import update_wrapper
|
|
|
2
2
|
import inspect
|
|
3
3
|
import re
|
|
4
4
|
|
|
5
|
-
from gjdutils.sorteddict import SortedDict
|
|
6
5
|
from gjdutils.typ import isiterable
|
|
7
6
|
|
|
8
7
|
|
|
@@ -51,7 +50,7 @@ def generate_mckey(prefix, d):
|
|
|
51
50
|
The idea is to ensure that no matter how D was
|
|
52
51
|
created, you'll know what the key should be.
|
|
53
52
|
"""
|
|
54
|
-
sorted_d =
|
|
53
|
+
sorted_d = dict()
|
|
55
54
|
for k in sorted(d.keys()):
|
|
56
55
|
sorted_d[k] = d[k]
|
|
57
56
|
return sorted_d
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
from collections import Counter
|
|
2
2
|
import itertools
|
|
3
|
-
import numpy as np
|
|
4
|
-
import pandas as pd
|
|
5
|
-
import random
|
|
6
|
-
from scipy import spatial, sparse
|
|
7
3
|
import sys
|
|
8
|
-
from typing import Any,
|
|
4
|
+
from typing import Any, Sequence
|
|
9
5
|
|
|
6
|
+
# keep this, because it makes sense for the user to be able to import from here
|
|
10
7
|
from .rand import set_seeds
|
|
11
8
|
|
|
12
9
|
|
|
13
10
|
def init_display_options():
|
|
11
|
+
import numpy as np
|
|
12
|
+
import pandas as pd
|
|
13
|
+
|
|
14
14
|
# so that it's easier to see things in the terminal
|
|
15
15
|
pd.set_option("display.max_rows", 10000)
|
|
16
16
|
pd.set_option("display.max_columns", 1000)
|
|
@@ -74,6 +74,8 @@ def calc_normalised_std_tightness(vals: Sequence[float]) -> float:
|
|
|
74
74
|
[19, 1, 40, 20] -> 0.31
|
|
75
75
|
[ 9, 1, 70, 0] -> 0
|
|
76
76
|
"""
|
|
77
|
+
import numpy as np
|
|
78
|
+
|
|
77
79
|
n = len(vals)
|
|
78
80
|
if n == 0:
|
|
79
81
|
raise Exception("Empty")
|
|
@@ -143,6 +145,8 @@ def convert_sim_dist_oneminus(val: float) -> float:
|
|
|
143
145
|
|
|
144
146
|
|
|
145
147
|
def square_df_from_square(sq, features):
|
|
148
|
+
import pandas as pd
|
|
149
|
+
|
|
146
150
|
df = pd.DataFrame(sq)
|
|
147
151
|
# create a 'Feature' column
|
|
148
152
|
df["Feature"] = features
|
|
@@ -154,6 +158,8 @@ def square_df_from_square(sq, features):
|
|
|
154
158
|
|
|
155
159
|
|
|
156
160
|
def long_df_from_flat(dists_flat, features):
|
|
161
|
+
import pandas as pd
|
|
162
|
+
|
|
157
163
|
combos = [(f1, f2) for f1, f2 in itertools.permutations(features, 2)]
|
|
158
164
|
assert len(combos) == len(dists_flat)
|
|
159
165
|
dists_triplet = [
|
|
@@ -164,6 +170,8 @@ def long_df_from_flat(dists_flat, features):
|
|
|
164
170
|
|
|
165
171
|
|
|
166
172
|
def square_df_from_flat(dists_flat, features):
|
|
173
|
+
from scipy import spatial
|
|
174
|
+
|
|
167
175
|
nFeatures = len(features)
|
|
168
176
|
dists_sq = spatial.distance.squareform(np.array(dists_flat))
|
|
169
177
|
assert dists_sq.shape == (nFeatures, nFeatures)
|
|
@@ -3,13 +3,13 @@ from pathlib import Path
|
|
|
3
3
|
from typing import Any, TypeVar, cast
|
|
4
4
|
from pydantic import StrictStr, TypeAdapter
|
|
5
5
|
|
|
6
|
-
from gjdutils.
|
|
6
|
+
from gjdutils.print_utils import vprint
|
|
7
7
|
|
|
8
8
|
T = TypeVar("T")
|
|
9
9
|
_processed_vars = set()
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
# You may find it useful to run `scripts
|
|
12
|
+
# You may find it useful to run `python -m gjdutils.scripts.export_all .env` to first
|
|
13
13
|
# export all the variables in your .env file to your environment.
|
|
14
14
|
|
|
15
15
|
|
|
@@ -4,7 +4,7 @@ from typing import Any
|
|
|
4
4
|
|
|
5
5
|
def func_name():
|
|
6
6
|
# https://stackoverflow.com/a/13514318/230523
|
|
7
|
-
return inspect.currentframe().f_back.f_code.co_name
|
|
7
|
+
return inspect.currentframe().f_back.f_code.co_name # type: ignore
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def variable_from_caller(var_name: str, frame_depth: int = 1) -> Any:
|
|
@@ -21,18 +21,18 @@ def variable_from_caller(var_name: str, frame_depth: int = 1) -> Any:
|
|
|
21
21
|
try:
|
|
22
22
|
# Go back the specified number of frames
|
|
23
23
|
for _ in range(frame_depth + 1):
|
|
24
|
-
if frame.f_back is None:
|
|
24
|
+
if frame.f_back is None: # type: ignore
|
|
25
25
|
raise ValueError(f"Cannot go back {frame_depth} frames")
|
|
26
|
-
frame = frame.f_back
|
|
26
|
+
frame = frame.f_back # type: ignore
|
|
27
27
|
|
|
28
|
-
if var_name not in frame.f_locals:
|
|
29
|
-
caller_name = frame.f_code.co_name
|
|
28
|
+
if var_name not in frame.f_locals: # type: ignore
|
|
29
|
+
caller_name = frame.f_code.co_name # type: ignore
|
|
30
30
|
raise ValueError(
|
|
31
31
|
f"Variable '{var_name}' not found in caller function '{caller_name}'. "
|
|
32
32
|
f"Make sure to define a '{var_name}' parameter or variable."
|
|
33
33
|
)
|
|
34
34
|
|
|
35
|
-
return frame.f_locals[var_name]
|
|
35
|
+
return frame.f_locals[var_name] # type: ignore
|
|
36
36
|
finally:
|
|
37
37
|
# Clean up circular references
|
|
38
38
|
del frame
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
from bs4 import BeautifulSoup
|
|
2
|
-
from lxml import
|
|
3
|
-
from lxml.
|
|
4
|
-
from
|
|
5
|
-
from typing import Optional, Iterable, Union
|
|
2
|
+
from lxml.html import tostring
|
|
3
|
+
from lxml.etree import _Element as ElementType
|
|
4
|
+
from typing import Optional, Union
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
def remove_html_tags(html: str):
|
|
@@ -65,15 +64,15 @@ def prettify_html(
|
|
|
65
64
|
if isinstance(html, list):
|
|
66
65
|
# then we'll handle it as a string in a moment
|
|
67
66
|
html = "".join(
|
|
68
|
-
[tostring(e, method="html").decode() for e in html]
|
|
67
|
+
[tostring(e, method="html").decode() for e in html] # type: ignore
|
|
69
68
|
) # type: ignore
|
|
70
69
|
if isinstance(html, ElementType):
|
|
71
70
|
# this will do some cleaning and fixing. but
|
|
72
71
|
# you need document_fromstring() if you want to make sure
|
|
73
72
|
# that it's a full html doc, e.g. with html, body
|
|
74
|
-
html = tostring(html, method="html").decode()
|
|
73
|
+
html = tostring(html, method="html").decode() # type: ignore
|
|
75
74
|
|
|
76
|
-
soup = BeautifulSoup(html, "html.parser")
|
|
75
|
+
soup = BeautifulSoup(html, "html.parser") # type: ignore
|
|
77
76
|
# html2 = tostring(html, pretty_print=True, method="html").decode() # type: ignore
|
|
78
77
|
# the lxml pretty_print just isn't as good as BS4, e.g. with a list of elements
|
|
79
78
|
# it wraps things in a div fragment, but the pretty-print of that isn't right
|
|
@@ -6,10 +6,12 @@ def jsonify(x):
|
|
|
6
6
|
def json_dumper_robust(obj):
|
|
7
7
|
try:
|
|
8
8
|
return obj.toJSON()
|
|
9
|
-
except:
|
|
9
|
+
except AttributeError:
|
|
10
|
+
# Object doesn't have toJSON method, try string conversion
|
|
10
11
|
try:
|
|
11
12
|
return str(obj)
|
|
12
|
-
except:
|
|
13
|
+
except (ValueError, TypeError):
|
|
14
|
+
# If string conversion fails, return None to skip this field
|
|
13
15
|
return None
|
|
14
16
|
|
|
15
17
|
return json.dumps(x, sort_keys=True, indent=4, default=json_dumper_robust)
|
|
@@ -49,6 +51,8 @@ def to_json(
|
|
|
49
51
|
Convert a list of dicts to a JSON string, with only the fields we want,
|
|
50
52
|
and in the same order as FIELDS.
|
|
51
53
|
"""
|
|
54
|
+
if fields is None:
|
|
55
|
+
fields = []
|
|
52
56
|
outs = []
|
|
53
57
|
for inp in inps:
|
|
54
58
|
if fields is None:
|
|
@@ -56,7 +60,7 @@ def to_json(
|
|
|
56
60
|
# we want to make sure to return a dict with only the fields we want,
|
|
57
61
|
# and in the same order as FIELDS
|
|
58
62
|
out = {}
|
|
59
|
-
for k in fields:
|
|
63
|
+
for k in fields: # type: ignore
|
|
60
64
|
if skip_if_missing and (k not in inp):
|
|
61
65
|
continue
|
|
62
66
|
v = inp[k] # will error if missing and !SKIP_IF_MISSING
|
|
@@ -33,9 +33,9 @@ def proc_llm_out_json(s: str):
|
|
|
33
33
|
s = s[7:-3]
|
|
34
34
|
try:
|
|
35
35
|
j = json.loads(s)
|
|
36
|
-
except:
|
|
37
|
-
print(s)
|
|
38
|
-
raise
|
|
36
|
+
except json.JSONDecodeError:
|
|
37
|
+
print("Failed to parse JSON:", s)
|
|
38
|
+
raise
|
|
39
39
|
return j
|
|
40
40
|
|
|
41
41
|
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from anthropic import Anthropic, NOT_GIVEN
|
|
4
|
-
import os
|
|
5
4
|
from typing import Optional
|
|
6
5
|
|
|
7
|
-
from gjdutils.rand import DEFAULT_RANDOM_SEED
|
|
8
6
|
from gjdutils.llm_utils import image_to_base64_basic
|
|
7
|
+
from gjdutils.env import get_env_var
|
|
9
8
|
|
|
10
|
-
CLAUDE_API_KEY =
|
|
9
|
+
CLAUDE_API_KEY = get_env_var("CLAUDE_API_KEY")
|
|
11
10
|
# https://docs.anthropic.com/en/docs/about-claude/models
|
|
12
11
|
MODEL_NAME_CLAUDE_SONNET_GOOD_PINNED = "claude-3-5-sonnet-20240620"
|
|
13
12
|
MODEL_NAME_CLAUDE_SONNET_GOOD_LATEST = "claude-3-5-sonnet-latest"
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from openai import OpenAI, NOT_GIVEN
|
|
3
|
-
import
|
|
4
|
-
from pprint import pprint
|
|
5
|
-
from typing import Any, Literal, Optional
|
|
3
|
+
from typing import Literal, Optional
|
|
6
4
|
|
|
7
5
|
from .prompt_templates import summarise_list_of_texts_as_one, summarise_text
|
|
8
6
|
from .rand import DEFAULT_RANDOM_SEED
|
|
9
7
|
from .strings import jinja_render
|
|
10
8
|
from gjdutils.llm_utils import contents_for_images
|
|
9
|
+
from gjdutils.env import get_env_var
|
|
11
10
|
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
|
|
12
|
+
OPENAI_API_KEY = get_env_var("OPENAI_API_KEY")
|
|
15
13
|
MODEL_NAME_GPT4 = "gpt-4"
|
|
16
14
|
MODEL_NAME_GPT35 = "gpt-3.5-turbo"
|
|
17
15
|
MODEL_NAME_GPT4_TURBO = "gpt-4-turbo" # -1106-preview"
|
|
@@ -20,7 +20,7 @@ def discretise(
|
|
|
20
20
|
increment: Union[int, float] = 0.1,
|
|
21
21
|
lower: Union[int, float] = 0.0,
|
|
22
22
|
upper: Union[int, float] = 1.0,
|
|
23
|
-
enforce_range: bool =
|
|
23
|
+
enforce_range: bool = True,
|
|
24
24
|
):
|
|
25
25
|
"""
|
|
26
26
|
You will probably want to cache this.
|
|
@@ -40,10 +40,10 @@ def discretise(
|
|
|
40
40
|
|
|
41
41
|
if pd.isnull(val):
|
|
42
42
|
return upper
|
|
43
|
-
if enforce_range:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
)
|
|
43
|
+
if enforce_range and (val < lower or val > upper):
|
|
44
|
+
raise ValueError(
|
|
45
|
+
f"Value {val:.2f} is outside the valid range [{lower:.2f}, {upper:.2f}]"
|
|
46
|
+
)
|
|
47
47
|
increments = calc_increments(increment, lower, upper)
|
|
48
48
|
if val < lower:
|
|
49
49
|
return increments[0]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from typing import Optional
|
|
3
|
+
from gjdutils.env import get_env_var
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
def outloud(
|
|
@@ -33,11 +34,12 @@ def outloud(
|
|
|
33
34
|
verbose=verbose,
|
|
34
35
|
)
|
|
35
36
|
elif prog == "elevenlabs":
|
|
37
|
+
assert speed is None, "speed not supported for elevenlabs"
|
|
36
38
|
response = outloud_elevenlabs(
|
|
37
39
|
text=text,
|
|
38
40
|
mp3_filen=mp3_filen,
|
|
39
41
|
bot_name=bot_name,
|
|
40
|
-
speed=speed,
|
|
42
|
+
# speed=speed,
|
|
41
43
|
verbose=verbose,
|
|
42
44
|
)
|
|
43
45
|
else:
|
|
@@ -65,8 +67,8 @@ def outloud_azure(
|
|
|
65
67
|
|
|
66
68
|
# This example requires environment variables named "SPEECH_KEY" and "SPEECH_REGION"
|
|
67
69
|
speech_config = speechsdk.SpeechConfig(
|
|
68
|
-
subscription=
|
|
69
|
-
region=
|
|
70
|
+
subscription=get_env_var("SPEECH_KEY"),
|
|
71
|
+
region=get_env_var("SPEECH_REGION"),
|
|
70
72
|
)
|
|
71
73
|
# https://learn.microsoft.com/en-us/answers/questions/693848/can-azure-text-to-speech-support-more-audio-files.html
|
|
72
74
|
speech_config.set_speech_synthesis_output_format(
|
|
@@ -78,7 +80,7 @@ def outloud_azure(
|
|
|
78
80
|
|
|
79
81
|
# The language of the voice that speaks.
|
|
80
82
|
# speech_config.speech_synthesis_voice_name = "en-US-JennyNeural"
|
|
81
|
-
speech_config.speech_synthesis_voice_name = bot_name
|
|
83
|
+
speech_config.speech_synthesis_voice_name = bot_name # type: ignore
|
|
82
84
|
|
|
83
85
|
speech_synthesizer = speechsdk.SpeechSynthesizer(
|
|
84
86
|
speech_config=speech_config, audio_config=audio_config
|
|
@@ -100,13 +102,13 @@ def outloud_azure(
|
|
|
100
102
|
speech_synthesis_result = speech_synthesizer.speak_ssml_async(ssml).get()
|
|
101
103
|
|
|
102
104
|
if (
|
|
103
|
-
speech_synthesis_result.reason
|
|
105
|
+
speech_synthesis_result.reason # type: ignore
|
|
104
106
|
== speechsdk.ResultReason.SynthesizingAudioCompleted
|
|
105
107
|
):
|
|
106
108
|
if verbose > 0:
|
|
107
109
|
print("Speech synthesized for text [{}]".format(text))
|
|
108
|
-
elif speech_synthesis_result.reason == speechsdk.ResultReason.Canceled:
|
|
109
|
-
cancellation_details = speech_synthesis_result.cancellation_details
|
|
110
|
+
elif speech_synthesis_result.reason == speechsdk.ResultReason.Canceled: # type: ignore
|
|
111
|
+
cancellation_details = speech_synthesis_result.cancellation_details # type: ignore
|
|
110
112
|
print("Speech synthesis canceled: {}".format(cancellation_details.reason))
|
|
111
113
|
if cancellation_details.reason == speechsdk.CancellationReason.Error:
|
|
112
114
|
if cancellation_details.error_details:
|
|
@@ -193,7 +195,7 @@ def outloud_elevenlabs(
|
|
|
193
195
|
from elevenlabs.client import ElevenLabs
|
|
194
196
|
|
|
195
197
|
if api_key is None:
|
|
196
|
-
api_key =
|
|
198
|
+
api_key = get_env_var("ELEVENLABS_API_KEY")
|
|
197
199
|
# i should figure out a better way to handle playing an mp3 file
|
|
198
200
|
# assert (
|
|
199
201
|
# not mp3_filen and should_play
|
|
@@ -2,9 +2,12 @@ from gjdutils.functions import variable_from_caller
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
def vprint(min_verbosity: int, msg: str, /, **kwargs):
|
|
5
|
-
"""Print if the caller's verbosity level is >= min_verbosity.
|
|
5
|
+
"""Print if the caller's verbosity level is >= min_verbosity, e.g.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
verbose = 2
|
|
8
|
+
vprint(1, "Hello, world!") # prints because verbose >= 1
|
|
9
|
+
|
|
10
|
+
This function looks for a `verbose` variable in the caller's scope.
|
|
8
11
|
|
|
9
12
|
Args:
|
|
10
13
|
min_verbosity: Minimum verbosity level required to print (positional-only)
|
|
@@ -4,16 +4,30 @@ from pathlib import Path
|
|
|
4
4
|
import urllib.request
|
|
5
5
|
import urllib.error
|
|
6
6
|
import shutil
|
|
7
|
+
import tomllib
|
|
7
8
|
from typing import Literal
|
|
8
9
|
from rich.console import Console
|
|
9
10
|
from rich.progress import track
|
|
10
11
|
from packaging.version import Version
|
|
12
|
+
from importlib.metadata import metadata
|
|
11
13
|
|
|
12
14
|
from gjdutils.cmd import run_cmd
|
|
13
|
-
from gjdutils import __version__
|
|
14
15
|
|
|
15
16
|
console = Console()
|
|
16
17
|
|
|
18
|
+
|
|
19
|
+
def get_version() -> str:
|
|
20
|
+
"""Get package version from pyproject.toml.
|
|
21
|
+
|
|
22
|
+
We read directly from pyproject.toml rather than using Python's packaging machinery
|
|
23
|
+
because we've had issues with the correct version being accessed in deployment/checking
|
|
24
|
+
machinery that creates virtualenvs. This provides a more reliable way to access the
|
|
25
|
+
true version from the source."""
|
|
26
|
+
with open("pyproject.toml", "rb") as f:
|
|
27
|
+
data = tomllib.load(f)
|
|
28
|
+
return data["project"]["version"]
|
|
29
|
+
|
|
30
|
+
|
|
17
31
|
# Type for PyPI environment
|
|
18
32
|
PyPIEnv = Literal["test", "prod"]
|
|
19
33
|
|
|
@@ -25,26 +39,27 @@ def verify_installation(python_path: Path):
|
|
|
25
39
|
before_msg="Verify package installation by importing and checking version...",
|
|
26
40
|
fatal_msg="Failed to import gjdutils",
|
|
27
41
|
)
|
|
42
|
+
expected_version = get_version()
|
|
28
43
|
assert (
|
|
29
|
-
installed_version ==
|
|
30
|
-
), f"Installed version {installed_version} does not match expected version {
|
|
44
|
+
installed_version == expected_version
|
|
45
|
+
), f"Installed version {installed_version} does not match expected version {expected_version}"
|
|
31
46
|
console.print(f"gjdutils version: {installed_version}")
|
|
32
47
|
return installed_version
|
|
33
48
|
|
|
34
49
|
|
|
35
50
|
def check_install_optional_features(python_path: Path, *, from_test_pypi: bool = False):
|
|
36
51
|
"""Test installation of optional feature sets."""
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
]
|
|
52
|
+
# Get optional dependency groups from package metadata
|
|
53
|
+
pkg_metadata = metadata("gjdutils")
|
|
54
|
+
# Parse the provides-extra field to get optional dependency groups
|
|
55
|
+
# get_all() returns None if the field doesn't exist
|
|
56
|
+
extra_features = pkg_metadata.get_all("Provides-Extra") or []
|
|
57
|
+
features = [group for group in extra_features if group not in ["dev", "all_no_dev"]]
|
|
43
58
|
|
|
44
59
|
for feature in track(features, description="Installing features"):
|
|
45
60
|
console.print(f"\nTesting feature set: {feature}", style="yellow")
|
|
46
61
|
if from_test_pypi:
|
|
47
|
-
cmd = f"{python_path} -m pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ gjdutils"
|
|
62
|
+
cmd = f"{python_path} -m pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ gjdutils[{feature}]"
|
|
48
63
|
else:
|
|
49
64
|
cmd = f"{python_path} -m pip install '.[{feature}]'"
|
|
50
65
|
run_cmd(
|
|
@@ -121,7 +121,7 @@ def jinja_render(
|
|
|
121
121
|
|
|
122
122
|
Will raise an error if CONTEXT is missing any variables.
|
|
123
123
|
"""
|
|
124
|
-
from jinja2 import Environment, FileSystemLoader, StrictUndefined
|
|
124
|
+
from jinja2 import Environment, FileSystemLoader, StrictUndefined
|
|
125
125
|
|
|
126
126
|
loader = None if filesystem_loader is None else FileSystemLoader(filesystem_loader)
|
|
127
127
|
env = Environment(loader=loader, undefined=StrictUndefined)
|
|
@@ -200,11 +200,18 @@ def append_fullstop(s: str):
|
|
|
200
200
|
|
|
201
201
|
|
|
202
202
|
def str_from_num(x):
|
|
203
|
+
try:
|
|
204
|
+
import numpy as np
|
|
205
|
+
|
|
206
|
+
bools = (bool, np.bool_)
|
|
207
|
+
except ImportError:
|
|
208
|
+
bools = (bool,)
|
|
209
|
+
|
|
203
210
|
# todo, find a better way of doing this, e.g.
|
|
204
211
|
# if isinstance(Iterable), recur. otherwise try str(x)
|
|
205
212
|
if x is None:
|
|
206
213
|
return str(x)
|
|
207
|
-
elif isinstance(x,
|
|
214
|
+
elif isinstance(x, bools):
|
|
208
215
|
return str(x)
|
|
209
216
|
elif isinstance(x, str):
|
|
210
217
|
return x
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# keep this, because it makes sense for the user to be able to import from here
|
|
1
2
|
from inspect import isfunction
|
|
2
3
|
|
|
3
4
|
# these are occasionally useful for if statements,
|
|
@@ -32,6 +33,6 @@ def isiterable(x):
|
|
|
32
33
|
"""
|
|
33
34
|
from http://stackoverflow.com/questions/1952464/in-python-how-do-i-determine-if-a-variable-is-iterable
|
|
34
35
|
"""
|
|
35
|
-
import collections
|
|
36
|
+
import collections.abc
|
|
36
37
|
|
|
37
|
-
return isinstance(x, collections.Iterable)
|
|
38
|
+
return isinstance(x, collections.abc.Iterable)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from pytest import approx, raises
|
|
2
|
+
|
|
3
|
+
from gjdutils.num import discretise
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_discretise():
|
|
7
|
+
assert discretise(0.00, increment=0.05) == approx(0.00)
|
|
8
|
+
assert discretise(0.01, increment=0.05) == approx(0.00)
|
|
9
|
+
assert discretise(0.06, increment=0.05) == approx(0.05)
|
|
10
|
+
assert discretise(0.99, increment=0.05) == approx(0.95)
|
|
11
|
+
assert discretise(1.00, increment=0.05) == approx(1.00)
|
|
12
|
+
# check values outside the range
|
|
13
|
+
with raises(Exception):
|
|
14
|
+
discretise(5.00, increment=0.05)
|
|
15
|
+
with raises(Exception):
|
|
16
|
+
discretise(-1.00, increment=0.05)
|
|
17
|
+
assert discretise(-1.00, increment=0.05, enforce_range=False) == approx(0.00)
|
|
18
|
+
assert discretise(5.00, increment=0.05, enforce_range=False) == approx(1.00)
|
gjdutils-0.3.0/__VERSION__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.0"
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import sys
|
|
3
|
-
|
|
4
|
-
# Add the root directory to Python path to import __VERSION__
|
|
5
|
-
root_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
|
6
|
-
if root_dir not in sys.path:
|
|
7
|
-
sys.path.append(root_dir)
|
|
8
|
-
|
|
9
|
-
from __VERSION__ import __version__
|
|
10
|
-
|
|
11
|
-
# Export version at package level
|
|
12
|
-
__version__ = __version__
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# This dict is always sorted by `sortfunc` when you iterate over it.
|
|
2
|
-
#
|
|
3
|
-
# For example, if you have a dict of str -> Nodes, and you want to iterate over them in
|
|
4
|
-
# order of their `idx` attribute, you can do:
|
|
5
|
-
#
|
|
6
|
-
# d = SortedDict[str,Node](lambda node: node.idx)
|
|
7
|
-
# [insert, remove, Nodes]
|
|
8
|
-
# d.values() # this will always be sorted by idx
|
|
9
|
-
#
|
|
10
|
-
# based on code from ChatGPT
|
|
11
|
-
|
|
12
|
-
from typing import Any, Callable, Dict, Generic, TypeVar
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
# Define type variables for keys and values
|
|
16
|
-
K = TypeVar("K")
|
|
17
|
-
V = TypeVar("V")
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class SortedDict(Dict[K, V], Generic[K, V]):
|
|
21
|
-
def __init__(self, sortfunc: Callable[[V], Any], *args, **kwargs):
|
|
22
|
-
super().__init__(*args, **kwargs)
|
|
23
|
-
self.sortfunc = sortfunc
|
|
24
|
-
self._reorder()
|
|
25
|
-
|
|
26
|
-
def __setitem__(self, key, value):
|
|
27
|
-
super().__setitem__(key, value)
|
|
28
|
-
self._reorder()
|
|
29
|
-
|
|
30
|
-
def _reorder(self):
|
|
31
|
-
sorted_items = sorted(self.items(), key=lambda item: self.sortfunc(item[1]))
|
|
32
|
-
super().clear()
|
|
33
|
-
for key, value in sorted_items:
|
|
34
|
-
super().__setitem__(key, value)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|