GJDutils 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gjdutils/__init__.py +12 -0
- gjdutils/audios.py +39 -0
- gjdutils/cacheing.py +237 -0
- gjdutils/cmd.py +149 -0
- gjdutils/colab.py +39 -0
- gjdutils/collections.py +36 -0
- gjdutils/decorators.py +34 -0
- gjdutils/dicts.py +216 -0
- gjdutils/dsci.py +202 -0
- gjdutils/dt.py +296 -0
- gjdutils/env.py +64 -0
- gjdutils/errors.py +12 -0
- gjdutils/files.py +140 -0
- gjdutils/functions.py +6 -0
- gjdutils/google_translate.py +80 -0
- gjdutils/hashing.py +32 -0
- gjdutils/html.py +87 -0
- gjdutils/indexing.py +97 -0
- gjdutils/iterfunc.py +99 -0
- gjdutils/jsons.py +70 -0
- gjdutils/lists.py +13 -0
- gjdutils/llm_utils.py +167 -0
- gjdutils/llms_claude.py +131 -0
- gjdutils/llms_openai.py +299 -0
- gjdutils/misc.py +30 -0
- gjdutils/num.py +77 -0
- gjdutils/obsolete/google_text_to_speech.py +46 -0
- gjdutils/obsolete/llms_obsolete.py +298 -0
- gjdutils/outloud_text_to_speech.py +230 -0
- gjdutils/prompt_templates.py +20 -0
- gjdutils/pypi_build.py +112 -0
- gjdutils/pytest_utils.py +24 -0
- gjdutils/rand.py +65 -0
- gjdutils/regex.py +78 -0
- gjdutils/requirements_dev.txt +2 -0
- gjdutils/runtime.py +19 -0
- gjdutils/sets.py +5 -0
- gjdutils/shell.py +69 -0
- gjdutils/sorteddict.py +34 -0
- gjdutils/stopwatch.py +79 -0
- gjdutils/strings.py +218 -0
- gjdutils/todo/convert_parquet.py +28 -0
- gjdutils/typ.py +37 -0
- gjdutils/voice_speechrecognition.py +29 -0
- gjdutils/web.py +68 -0
- gjdutils-0.2.2.dist-info/METADATA +101 -0
- gjdutils-0.2.2.dist-info/RECORD +49 -0
- gjdutils-0.2.2.dist-info/WHEEL +4 -0
- gjdutils-0.2.2.dist-info/licenses/LICENSE +21 -0
gjdutils/pypi_build.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Shared utilities for PyPI package building and testing."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import urllib.request
|
|
5
|
+
import urllib.error
|
|
6
|
+
import shutil
|
|
7
|
+
from typing import Literal
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.progress import track
|
|
10
|
+
from packaging.version import Version
|
|
11
|
+
|
|
12
|
+
from gjdutils.cmd import run_cmd
|
|
13
|
+
from gjdutils import __version__
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
# Type for PyPI environment
|
|
18
|
+
PyPIEnv = Literal["test", "prod"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def verify_installation(python_path: Path):
|
|
22
|
+
# Command: python -c "import gjdutils; print(gjdutils.__version__)"
|
|
23
|
+
retcode, installed_version, extra = run_cmd(
|
|
24
|
+
f'{python_path} -c "import gjdutils; print(gjdutils.__version__)"',
|
|
25
|
+
before_msg="Verify package installation by importing and checking version...",
|
|
26
|
+
fatal_msg="Failed to import gjdutils",
|
|
27
|
+
)
|
|
28
|
+
assert (
|
|
29
|
+
installed_version == __version__
|
|
30
|
+
), f"Installed version {installed_version} does not match expected version {__version__}"
|
|
31
|
+
console.print(f"gjdutils version: {installed_version}")
|
|
32
|
+
return installed_version
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def check_install_optional_features(python_path: Path, *, from_test_pypi: bool = False):
|
|
36
|
+
"""Test installation of optional feature sets."""
|
|
37
|
+
features = [
|
|
38
|
+
"audio_lang",
|
|
39
|
+
"dt",
|
|
40
|
+
"llm",
|
|
41
|
+
"html_web",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
for feature in track(features, description="Installing features"):
|
|
45
|
+
console.print(f"\nTesting feature set: {feature}", style="yellow")
|
|
46
|
+
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"
|
|
48
|
+
else:
|
|
49
|
+
cmd = f"{python_path} -m pip install '.[{feature}]'"
|
|
50
|
+
run_cmd(
|
|
51
|
+
cmd,
|
|
52
|
+
before_msg=f"Installing feature set: {feature}...",
|
|
53
|
+
fatal_msg=f"Failed to install {feature} feature",
|
|
54
|
+
)
|
|
55
|
+
console.print(f"[green]Successfully installed {feature} feature[/green]")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def check_version_exists(version: Version, pypi_env: PyPIEnv) -> bool:
|
|
59
|
+
"""Check if version already exists on specified PyPI environment.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
version: Version string to check (must be valid semantic version)
|
|
63
|
+
pypi_env: PyPI environment to check ("test" or "prod")
|
|
64
|
+
"""
|
|
65
|
+
base_url = {
|
|
66
|
+
"test": "https://test.pypi.org",
|
|
67
|
+
"prod": "https://pypi.org",
|
|
68
|
+
}[pypi_env]
|
|
69
|
+
try:
|
|
70
|
+
url = f"{base_url}/pypi/gjdutils/{str(version)}/json"
|
|
71
|
+
urllib.request.urlopen(url)
|
|
72
|
+
return True
|
|
73
|
+
except urllib.error.HTTPError as e:
|
|
74
|
+
if e.code == 404:
|
|
75
|
+
return False
|
|
76
|
+
raise # Re-raise other HTTP errors
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def clean_build_dirs():
|
|
80
|
+
"""Clean build directories (dist/ and build/)."""
|
|
81
|
+
# Command: rm -rf dist/ build/
|
|
82
|
+
shutil.rmtree("dist", ignore_errors=True)
|
|
83
|
+
shutil.rmtree("build", ignore_errors=True)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def build_package():
|
|
87
|
+
"""Build package with python -m build."""
|
|
88
|
+
return run_cmd(
|
|
89
|
+
"python -m build",
|
|
90
|
+
before_msg="Building package...",
|
|
91
|
+
fatal_msg="Failed to build package",
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def upload_to_pypi(pypi_env: PyPIEnv):
|
|
96
|
+
"""Upload package to specified PyPI environment.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
pypi_env: PyPI environment to upload to ("test" or "prod")
|
|
100
|
+
"""
|
|
101
|
+
if pypi_env == "test":
|
|
102
|
+
cmd = "twine upload -r testpypi dist/*"
|
|
103
|
+
elif pypi_env == "prod":
|
|
104
|
+
cmd = "twine upload dist/*"
|
|
105
|
+
else:
|
|
106
|
+
raise ValueError(f"Invalid PyPI environment: {pypi_env}")
|
|
107
|
+
|
|
108
|
+
return run_cmd(
|
|
109
|
+
cmd,
|
|
110
|
+
before_msg=f"Uploading package to {pypi_env} PyPI...",
|
|
111
|
+
fatal_msg=f"Failed to upload to {pypi_env} PyPI",
|
|
112
|
+
)
|
gjdutils/pytest_utils.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def is_pytest():
|
|
6
|
+
# https://stackoverflow.com/a/44595269/230523
|
|
7
|
+
#
|
|
8
|
+
# "Of course, this solution only works if the code you're trying to test does not use pytest itself.
|
|
9
|
+
mod_bool = "pytest" in sys.modules
|
|
10
|
+
|
|
11
|
+
# # from https://stackoverflow.com/a/58866220/230523
|
|
12
|
+
# #
|
|
13
|
+
# # "This method works only when an actual test is being run.
|
|
14
|
+
# # "This detection will not work when modules are imported during pytest collection.
|
|
15
|
+
# env_bool = "PYTEST_CURRENT_TEST" in os.environ
|
|
16
|
+
# if mod_bool and env_bool:
|
|
17
|
+
# return True
|
|
18
|
+
# elif not mod_bool and not env_bool:
|
|
19
|
+
# return False
|
|
20
|
+
# else:
|
|
21
|
+
# raise RuntimeError(
|
|
22
|
+
# "It's unclear whether we're in a unit test - it might be part of the pytest setup, or you might have imported pytest as part of your main codebase."
|
|
23
|
+
# )
|
|
24
|
+
return mod_bool
|
gjdutils/rand.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from typing import Optional
|
|
3
|
+
import uuid
|
|
4
|
+
|
|
5
|
+
DEFAULT_RANDOM_SEED = 42
|
|
6
|
+
READABLE_RAND_CHARS = "23456789abcdefghjkmnprstuvwxyz"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def gen_uuid(n: int = 8):
|
|
10
|
+
# e.g. '161b58a4d8'
|
|
11
|
+
return str(uuid.uuid4()).replace("-", "")[:n]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def gen_readable_uuid(n: int = 7, valid: Optional[list | str] = None):
|
|
15
|
+
# build az N digit random string with letters or numbers
|
|
16
|
+
# excluding o,0,i,1,l,q (too similar to g) for easy reading
|
|
17
|
+
#
|
|
18
|
+
# so this isn't officially a UUID
|
|
19
|
+
if valid is None:
|
|
20
|
+
valid = READABLE_RAND_CHARS
|
|
21
|
+
return "".join([random.choice(valid) for _ in range(n)])
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def assert_valid_readable_rand_id(
|
|
25
|
+
id_: str, nchars: Optional[int] = None, valid_chars: Optional[str] = None
|
|
26
|
+
):
|
|
27
|
+
"""
|
|
28
|
+
Check if ID_ is a valid readable random id, as generated by GEN_READABLE_UUID.
|
|
29
|
+
"""
|
|
30
|
+
assert id_, "id_ cannot be empty"
|
|
31
|
+
if nchars is not None:
|
|
32
|
+
assert len(id_) == nchars, f"len({id_}) != {nchars}"
|
|
33
|
+
if valid_chars is None:
|
|
34
|
+
valid_chars = READABLE_RAND_CHARS
|
|
35
|
+
assert all([c in valid_chars for c in id_]), f"Invalid character in {id_}"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def shuffle_copy(lst):
|
|
39
|
+
lst2 = lst.copy()
|
|
40
|
+
random.shuffle(lst2)
|
|
41
|
+
return lst2
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def set_seeds(seed: int = DEFAULT_RANDOM_SEED):
|
|
45
|
+
# TODO if SEED is None, make it dynamic
|
|
46
|
+
random.seed(seed)
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
import numpy as np
|
|
50
|
+
|
|
51
|
+
np.random.seed(seed)
|
|
52
|
+
except ImportError:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
import torch # type: ignore
|
|
57
|
+
|
|
58
|
+
torch.manual_seed(seed)
|
|
59
|
+
torch.cuda.manual_seed(seed)
|
|
60
|
+
torch.cuda.manual_seed_all(seed)
|
|
61
|
+
# If you are using cudnn (a GPU-accelerated library for deep neural networks)
|
|
62
|
+
torch.backends.cudnn.deterministic = True
|
|
63
|
+
|
|
64
|
+
except ImportError:
|
|
65
|
+
pass
|
gjdutils/regex.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Iterable
|
|
3
|
+
|
|
4
|
+
# Based on:
|
|
5
|
+
# https://github.com/gregdetre/emacs-freex/blob/63523bf3b9032cc75b55ee28929dcdaf7714a419/freex_sqlalchemy.py#L653
|
|
6
|
+
# https://github.com/gregdetre/emacs-freex/blob/63523bf3b9032cc75b55ee28929dcdaf7714a419/freex_sqlalchemy.py#L713
|
|
7
|
+
# That code has faster versions for much bigger lists of aliases, but this is fine for now.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def compile_regex_for_matching_aliases(aliases: Iterable[str]):
|
|
11
|
+
"""
|
|
12
|
+
Builds and compiles the implicit link regular expression.
|
|
13
|
+
"""
|
|
14
|
+
# add .lower() here and elsewhere if you want case-insensitive
|
|
15
|
+
aliases = [re.escape(a) for a in aliases]
|
|
16
|
+
|
|
17
|
+
# ensure that 'jubba wubba' comes before 'jubba'
|
|
18
|
+
aliases.sort(reverse=True)
|
|
19
|
+
|
|
20
|
+
# build the regexp string to be single words
|
|
21
|
+
#
|
|
22
|
+
# it used to look like this, which worked nicely, unless
|
|
23
|
+
# there was a carriage return
|
|
24
|
+
alias_regex_str = "\\b" + "\\b|\\b".join(aliases) + "\\b"
|
|
25
|
+
|
|
26
|
+
# we want to deal with the possibility that there are 0
|
|
27
|
+
# or 1 spaces after a word, followed by 0 or 1 carriage
|
|
28
|
+
# returns, followed by zero or more spaces, which is
|
|
29
|
+
# what might happen if an implicit link was to span two
|
|
30
|
+
# lines in an indented paragraph. that's what ' ?\n? *' does
|
|
31
|
+
#
|
|
32
|
+
# the \\b that gets
|
|
33
|
+
# added later will ensure that there's a word boundary
|
|
34
|
+
# of *some* kind.
|
|
35
|
+
alias_regex_str = alias_regex_str.replace("\\ ", " ?\\\n? *")
|
|
36
|
+
|
|
37
|
+
# for ages, it wasn't matching things like 'Smith &
|
|
38
|
+
# Jones (2006)', because there was some problem with the
|
|
39
|
+
# parentheses. i eventually realized that it matched the
|
|
40
|
+
# first, but not the second parenthesis, because (i
|
|
41
|
+
# think) the parenthesis was screwing with the \b (bare
|
|
42
|
+
# word) separator. if you remove all the bare word
|
|
43
|
+
# separators that follow closing parentheses, the sun
|
|
44
|
+
# comes back out again
|
|
45
|
+
alias_regex_str = alias_regex_str.replace(")\\b", ")")
|
|
46
|
+
|
|
47
|
+
# compile the regexp
|
|
48
|
+
# impLinkRegexp = re.compile(aliasRegexpStr, re.IGNORECASE|re.MULTILINE)
|
|
49
|
+
compiled_regex = re.compile(alias_regex_str, re.MULTILINE)
|
|
50
|
+
return compiled_regex
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def find_matchranges_for_aliases(
|
|
54
|
+
compiled_regex_of_aliases: re.Pattern, txt_to_search: str
|
|
55
|
+
):
|
|
56
|
+
"""
|
|
57
|
+
Return a list of (beg,end) tuples for all the matching implicit
|
|
58
|
+
links in the provided string.
|
|
59
|
+
"""
|
|
60
|
+
# get the start and endpoints for the matchranges
|
|
61
|
+
matchranges = [
|
|
62
|
+
list(match.span())
|
|
63
|
+
for match in compiled_regex_of_aliases.finditer(txt_to_search)
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
# return the matchranges
|
|
67
|
+
return matchranges
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# txt = """There was a young European man called Friedrich Nietzsche, who most went by just "Nietzsche" (and but never 'Friedrich'). He was a friend of Little Hans and Little Richard but he was not little or Little."""
|
|
71
|
+
|
|
72
|
+
# aliasRegexpStr, impLinkRegexp = update_implicit_link_regexp_original(all_aliases)
|
|
73
|
+
# match_ranges = get_all_matching_implicit_links_original(impLinkRegexp, txt)
|
|
74
|
+
|
|
75
|
+
# for match_range in match_ranges:
|
|
76
|
+
# match_txt = txt[match_range[0]:match_range[1]]
|
|
77
|
+
# matched_name = name_from_alias[match_txt]
|
|
78
|
+
# print('MATCHED: ', match_txt, ' <- NAME: ', matched_name, sep='')
|
gjdutils/runtime.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def in_notebook() -> bool:
|
|
5
|
+
# from https://stackoverflow.com/q/15411967
|
|
6
|
+
try:
|
|
7
|
+
shell = get_ipython().__class__.__name__ # type: ignore
|
|
8
|
+
if shell == "ZMQInteractiveShell":
|
|
9
|
+
return True # Jupyter notebook or qtconsole
|
|
10
|
+
elif shell == "TerminalInteractiveShell":
|
|
11
|
+
return False # Terminal running IPython
|
|
12
|
+
else:
|
|
13
|
+
return False # Other type (?)
|
|
14
|
+
except NameError:
|
|
15
|
+
return False # Probably standard Python interpreter
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def in_colab():
|
|
19
|
+
return "google.colab" in sys.modules
|
gjdutils/sets.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
def assert_sets_identical(set1: set, set2: set):
|
|
2
|
+
# assert that the sets are the same, and print the difference if not
|
|
3
|
+
if set1 != set2:
|
|
4
|
+
assert not set1 - set2, f"Set 1 has extra elements: {set1 - set2}"
|
|
5
|
+
assert not set2 - set1, f"Set 2 has extra elements: {set2 - set1}"
|
gjdutils/shell.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Shell and command-line utilities."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
import venv
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
from typing import Optional, Union
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@contextmanager
|
|
13
|
+
def temp_venv(path: Union[str, Path]):
|
|
14
|
+
"""Create and manage a temporary virtualenv.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
path: Path where the virtualenv should be created
|
|
18
|
+
|
|
19
|
+
Yields:
|
|
20
|
+
Path to the Python executable in the virtualenv
|
|
21
|
+
|
|
22
|
+
Example:
|
|
23
|
+
```python
|
|
24
|
+
with temp_venv("/tmp/my-venv") as python_path:
|
|
25
|
+
run_cmd([python_path, "-m", "pip", "install", "some-package"])
|
|
26
|
+
```
|
|
27
|
+
"""
|
|
28
|
+
path = Path(path)
|
|
29
|
+
|
|
30
|
+
# Clean up any existing venv first
|
|
31
|
+
if path.exists():
|
|
32
|
+
shutil.rmtree(path)
|
|
33
|
+
|
|
34
|
+
venv.create(path, with_pip=True)
|
|
35
|
+
|
|
36
|
+
# Get the correct python executable path for this venv
|
|
37
|
+
if sys.platform == "win32":
|
|
38
|
+
python_path = path / "Scripts" / "python.exe"
|
|
39
|
+
else:
|
|
40
|
+
python_path = path / "bin" / "python"
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
yield python_path
|
|
44
|
+
finally:
|
|
45
|
+
if path.exists():
|
|
46
|
+
shutil.rmtree(path)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def fatal_error_msg(msg: str, stderr: Optional[str] = None) -> None:
|
|
50
|
+
"""Print a fatal error message and exit with code 1.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
msg: The error message to display
|
|
54
|
+
stderr: Optional stderr output to display after the message
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
```python
|
|
58
|
+
if result.returncode != 0:
|
|
59
|
+
fatal_error_msg("Failed to build package", result.stderr)
|
|
60
|
+
```
|
|
61
|
+
"""
|
|
62
|
+
from rich.console import Console
|
|
63
|
+
|
|
64
|
+
console = Console()
|
|
65
|
+
|
|
66
|
+
console.print(f"[red]{msg}[/red]")
|
|
67
|
+
if stderr:
|
|
68
|
+
console.print(stderr)
|
|
69
|
+
sys.exit(1)
|
gjdutils/sorteddict.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
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)
|
gjdutils/stopwatch.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
########################################################
|
|
2
|
+
# from http://effbot.org/librarybook/timing.htm
|
|
3
|
+
# File: timing-example-2.py
|
|
4
|
+
#
|
|
5
|
+
# copied from ~/fmri/distpat/trunk/users/greg/context/time_context.py
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
This is my wrapper for the time module. There's probably an
|
|
9
|
+
easier way to time the duration of things, but when I looked
|
|
10
|
+
into timing stuff, this was the best I could come up with...
|
|
11
|
+
|
|
12
|
+
To use:
|
|
13
|
+
|
|
14
|
+
t = Stopwatch()
|
|
15
|
+
# t.start()
|
|
16
|
+
|
|
17
|
+
# do something
|
|
18
|
+
|
|
19
|
+
elapsed = t.finish()
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import time
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Stopwatch:
|
|
26
|
+
"""
|
|
27
|
+
Creates stopwatch timer objects.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
# stores the time the stopwatch was started
|
|
31
|
+
t0 = None
|
|
32
|
+
|
|
33
|
+
# stores the time the stopwatch was last looked at
|
|
34
|
+
t1 = None
|
|
35
|
+
|
|
36
|
+
def __init__(self, do_start: bool = True):
|
|
37
|
+
self.t0 = 0
|
|
38
|
+
self.t1 = 0
|
|
39
|
+
if do_start:
|
|
40
|
+
self.start()
|
|
41
|
+
|
|
42
|
+
def start(self):
|
|
43
|
+
"""
|
|
44
|
+
Stores the current time in t0.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
self.t0 = time.time()
|
|
48
|
+
|
|
49
|
+
def finish(self, milliseconds: bool = True):
|
|
50
|
+
"""
|
|
51
|
+
Returns the elapsed duration in milliseconds. This
|
|
52
|
+
stores the current time in t1, and calculates the
|
|
53
|
+
difference between t0 (the stored start time) and
|
|
54
|
+
t1, so if you call this multiple times, you'll get a
|
|
55
|
+
larger answer each time.
|
|
56
|
+
|
|
57
|
+
You have to call this in order to update t1.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
self.t1 = time.time()
|
|
61
|
+
return self.milli() if milliseconds else self.seconds()
|
|
62
|
+
|
|
63
|
+
def seconds(self):
|
|
64
|
+
"""
|
|
65
|
+
Returns t1 - t0 in seconds. Does not update t1.
|
|
66
|
+
"""
|
|
67
|
+
return int(self.t1 - self.t0)
|
|
68
|
+
|
|
69
|
+
def milli(self):
|
|
70
|
+
"""
|
|
71
|
+
Returns t1 - t0 in milliseconds. Does not update t1.
|
|
72
|
+
"""
|
|
73
|
+
return int((self.t1 - self.t0) * 1000)
|
|
74
|
+
|
|
75
|
+
def micro(self):
|
|
76
|
+
"""
|
|
77
|
+
Returns t1 - t0 in microseconds. Does not update t1.
|
|
78
|
+
"""
|
|
79
|
+
return int((self.t1 - self.t0) * 1000000)
|