pynight 0.2.2__tar.gz → 0.2.3__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 (83) hide show
  1. {pynight-0.2.2 → pynight-0.2.3}/PKG-INFO +3 -3
  2. pynight-0.2.3/pynight/batched_iterable.py +101 -0
  3. pynight-0.2.3/pynight/common_anthropic.py +33 -0
  4. pynight-0.2.3/pynight/common_arxiv.py +21 -0
  5. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_async.py +1 -0
  6. pynight-0.2.3/pynight/common_attr.py +225 -0
  7. pynight-0.2.3/pynight/common_base64.py +44 -0
  8. pynight-0.2.3/pynight/common_bells.py +21 -0
  9. pynight-0.2.3/pynight/common_clip.py +4 -0
  10. pynight-0.2.3/pynight/common_clipboard.py +34 -0
  11. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_combinatorics.py +1 -0
  12. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_condition.py +14 -6
  13. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_csv.py +1 -0
  14. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_datasets.py +47 -20
  15. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_datetime.py +1 -1
  16. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_debugging.py +2 -19
  17. pynight-0.2.3/pynight/common_debugging2.py +157 -0
  18. pynight-0.2.3/pynight/common_deepseek.py +35 -0
  19. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_dict.py +35 -0
  20. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_dynamic.py +40 -7
  21. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_executing.py +4 -2
  22. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_external.py +1 -0
  23. pynight-0.2.3/pynight/common_faith.py +154 -0
  24. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_fastapi.py +2 -3
  25. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_files.py +84 -7
  26. pynight-0.2.3/pynight/common_fzf.py +84 -0
  27. pynight-0.2.3/pynight/common_groq.py +34 -0
  28. pynight-0.2.3/pynight/common_health.py +19 -0
  29. pynight-0.2.3/pynight/common_hosts.py +23 -0
  30. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_ipython.py +2 -5
  31. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_iterable.py +23 -28
  32. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_jax.py +1 -0
  33. pynight-0.2.3/pynight/common_json.py +196 -0
  34. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_jupyter.py +14 -6
  35. pynight-0.2.3/pynight/common_latex.py +23 -0
  36. pynight-0.2.3/pynight/common_llm.py +76 -0
  37. pynight-0.2.3/pynight/common_model_name.py +27 -0
  38. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_networking.py +1 -3
  39. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_numpy.py +35 -4
  40. pynight-0.2.3/pynight/common_openai.py +702 -0
  41. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_package.py +7 -1
  42. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_plt.py +2 -1
  43. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_proxy.py +15 -13
  44. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_random.py +3 -3
  45. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_redirections.py +3 -3
  46. pynight-0.2.3/pynight/common_regex.py +43 -0
  47. pynight-0.2.3/pynight/common_rtl.py +24 -0
  48. pynight-0.2.3/pynight/common_seg.py +121 -0
  49. pynight-0.2.3/pynight/common_shell.py +24 -0
  50. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_sort.py +17 -4
  51. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_ss.py +14 -10
  52. pynight-0.2.3/pynight/common_str.py +89 -0
  53. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_telegram.py +29 -6
  54. pynight-0.2.3/pynight/common_threading.py +19 -0
  55. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_timm.py +163 -19
  56. pynight-0.2.3/pynight/common_together.py +46 -0
  57. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_torch.py +666 -30
  58. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_tqdm.py +43 -14
  59. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_tui.py +17 -13
  60. {pynight-0.2.2 → pynight-0.2.3}/pyproject.toml +3 -3
  61. {pynight-0.2.2 → pynight-0.2.3}/setup.py +3 -3
  62. pynight-0.2.2/pynight/common_bells.py +0 -8
  63. pynight-0.2.2/pynight/common_clipboard.py +0 -5
  64. pynight-0.2.2/pynight/common_json.py +0 -71
  65. pynight-0.2.2/pynight/common_openai.py +0 -162
  66. pynight-0.2.2/pynight/common_regex.py +0 -20
  67. pynight-0.2.2/pynight/common_str.py +0 -24
  68. {pynight-0.2.2 → pynight-0.2.3}/pynight/__init__.py +0 -0
  69. {pynight-0.2.2 → pynight-0.2.3}/pynight/async_url.py +0 -0
  70. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_benchmark.py +0 -0
  71. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_evaluate.py +0 -0
  72. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_feed.py +1 -1
  73. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_functional.py +0 -0
  74. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_hash.py +0 -0
  75. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_hf.py +0 -0
  76. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_icecream.py +0 -0
  77. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_lock_async.py +0 -0
  78. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_obj.py +0 -0
  79. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_print.py +0 -0
  80. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_spacy.py +0 -0
  81. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_torch.org +0 -0
  82. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_tqdm2.py +0 -0
  83. {pynight-0.2.2 → pynight-0.2.3}/pynight/common_uvicorn.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pynight
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: My Python utility library.
5
5
  License: MIT
6
6
  Author: NightMachinary
@@ -17,7 +17,7 @@ Requires-Dist: brish (>=0.3.3,<0.4.0)
17
17
  Requires-Dist: dnspython (>=2.1.0,<3.0.0)
18
18
  Requires-Dist: executing (>=1.2.0,<2.0.0)
19
19
  Requires-Dist: matplotlib (>=3.7.1,<4.0.0)
20
- Requires-Dist: openai (>=0.27.2,<0.28.0)
20
+ Requires-Dist: openai (>=1.14.2,<2.0.0)
21
21
  Requires-Dist: pyperclip (>=1.8.2,<2.0.0)
22
22
  Requires-Dist: spacy (>=3.5.1,<4.0.0)
23
- Requires-Dist: tiktoken (>=0.3.3,<0.4.0)
23
+ Requires-Dist: tiktoken (>=0.6,<0.7)
@@ -0,0 +1,101 @@
1
+ from collections.abc import Mapping
2
+ from pynight.common_icecream import ic
3
+ from pynight.common_dict import (
4
+ SimpleObject,
5
+ simple_obj,
6
+ BatchedDict,
7
+ )
8
+
9
+
10
+ class BatchedIterable:
11
+ def __init__(
12
+ self,
13
+ data,
14
+ batch_size,
15
+ drop_last_batch=False,
16
+ skip_none_p=True,
17
+ autoadjust_batch_size_mode=False,
18
+ # autoadjust_batch_size_mode=True,
19
+ ):
20
+ self.data = data
21
+ self.batch_size = batch_size
22
+ self.drop_last_batch = drop_last_batch
23
+ self.skip_none_p = skip_none_p
24
+ self.autoadjust_batch_size_mode = autoadjust_batch_size_mode
25
+ self.extras = []
26
+ #: We will drop the default empty `extras`, which might not be summable with `res`. Its only purpose is to make using `len(self.extras)` easy without having to check for None.
27
+
28
+ def __iter__(self):
29
+ length = len(self.data)
30
+ i = 0
31
+
32
+ while i < length or len(self.extras) >= self.batch_size:
33
+ # ic(type(self.extras))
34
+
35
+ if len(self.extras) >= self.batch_size:
36
+ #: Yield a batch from extras
37
+ res = self.extras[: self.batch_size]
38
+ self.extras = self.extras[self.batch_size :]
39
+ yield res
40
+ continue
41
+
42
+ advance_by = self.batch_size - len(self.extras)
43
+ res = self.data[i : i + advance_by]
44
+ # ic(type(res))
45
+ # ic(len(res), advance_by, len(self.extras))
46
+
47
+ i += advance_by
48
+
49
+ if self.skip_none_p and res is None:
50
+ continue
51
+
52
+ if self.autoadjust_batch_size_mode:
53
+ res_size = len(res)
54
+ if res_size < self.batch_size and self.autoadjust_batch_size_mode in [
55
+ True,
56
+ "grow",
57
+ ]:
58
+ #: Merge with extras
59
+ if len(self.extras) >= 1:
60
+ res = self.extras + res
61
+
62
+ if len(res) >= self.batch_size:
63
+ self.extras = res[self.batch_size :]
64
+ res = res[: self.batch_size]
65
+ else:
66
+ self.extras = res
67
+ continue
68
+ elif res_size > self.batch_size and self.autoadjust_batch_size_mode in [
69
+ True,
70
+ "shrink",
71
+ ]:
72
+ #: Keep extras for the next batch
73
+ if len(self.extras) >= 1:
74
+ self.extras += res[self.batch_size :]
75
+ else:
76
+ self.extras = res[self.batch_size :]
77
+
78
+ res = res[: self.batch_size]
79
+
80
+ #: This assertion should be true, you can enable it for debugging:
81
+ # if self.autoadjust_batch_size_mode is True:
82
+ # assert (
83
+ # len(res) == self.batch_size
84
+ # ), f"autoadjust_batch_size_mode has not adjusted the size: len={len(res)}, should_be={self.batch_size}"
85
+
86
+ # ic(len(res), self.batch_size, self.autoadjust_batch_size_mode)
87
+ yield res
88
+
89
+ if not self.drop_last_batch and len(self.extras) > 0:
90
+ #: Yield the remaining extras as the last batch
91
+ yield self.extras
92
+
93
+ def __len__(self):
94
+ length = len(self.data)
95
+ num_batches = length // self.batch_size
96
+ if not self.drop_last_batch:
97
+ num_batches += length % self.batch_size != 0
98
+ return num_batches
99
+
100
+
101
+ ##
@@ -0,0 +1,33 @@
1
+ import anthropic
2
+ from brish import z, zp
3
+
4
+
5
+ anthropic_client = None
6
+ ##
7
+ anthropic_key = None
8
+
9
+
10
+ def setup_anthropic_key():
11
+ global anthropic_key
12
+ global anthropic_client
13
+
14
+ anthropic_key = z("var-get anthropic_api_key").outrs
15
+ assert anthropic_key, "setup_anthropic_key: could not get Anthropic API key!"
16
+
17
+ anthropic_client = anthropic.Anthropic(
18
+ # defaults to os.environ.get("ANTHROPIC_API_KEY")
19
+ api_key=anthropic_key,
20
+ )
21
+ return anthropic_client
22
+
23
+
24
+ def anthropic_key_get():
25
+ global anthropic_key
26
+
27
+ if anthropic_key is None:
28
+ setup_anthropic_key()
29
+
30
+ return anthropic_key
31
+
32
+
33
+ ##
@@ -0,0 +1,21 @@
1
+ import re
2
+
3
+
4
+ def normalize_path_for_arxiv(
5
+ path,
6
+ *,
7
+ disallowed_regex=r"[ :&\\\"]",
8
+ ):
9
+ """
10
+ Normalize a file path by replacing spaces, slashes, colons, ampersands,
11
+ and backslashes with underscores.
12
+
13
+ Args:
14
+ path (str): The file path to normalize
15
+
16
+ Returns:
17
+ str: The normalized file path
18
+ """
19
+ path = re.sub(disallowed_regex, "_", path)
20
+
21
+ return path
@@ -2,6 +2,7 @@ import functools
2
2
  import asyncio
3
3
  from concurrent.futures import ThreadPoolExecutor
4
4
 
5
+
5
6
  ##
6
7
  def force_async(f):
7
8
  @functools.wraps(f)
@@ -0,0 +1,225 @@
1
+ import torch
2
+ import torch.nn.functional as F
3
+ import re
4
+
5
+ from pynight.common_icecream import ic
6
+ from pynight.common_iterable import to_iterable
7
+ from pynight.common_dict import simple_obj
8
+ from pynight.common_datasets import (
9
+ TransformResult,
10
+ )
11
+ from pynight.common_torch import (
12
+ rank_tensor,
13
+ )
14
+
15
+
16
+ ##
17
+ def normalize_map(
18
+ attributions,
19
+ normalize,
20
+ # outlier_quantile=0.1,
21
+ outlier_quantile=0,
22
+ num_prefix_tokens=0,
23
+ bias_token_p=False,
24
+ clone_p=True,
25
+ pixel_p=False,
26
+ ):
27
+ if pixel_p:
28
+ attributions_skipped = attributions
29
+ else:
30
+ attributions_skipped = attributions[..., num_prefix_tokens:] #: Skips CLS
31
+ if bias_token_p:
32
+ attributions_skipped = attributions_skipped[..., :-1]
33
+ #: Skips the bias token
34
+
35
+ # ic(num_prefix_tokens, attributions.shape, attributions_skipped.shape)
36
+
37
+ attributions_normalized = attributions_skipped
38
+ if clone_p:
39
+ attributions_normalized = attributions_normalized.clone()
40
+
41
+ normalize = to_iterable(normalize)
42
+
43
+ if normalize:
44
+ for normalizer in normalize:
45
+ #: Computing these quantiles is expensive, so we do it only when needed.
46
+ if normalizer in [
47
+ "scale_by_max_signed_attr",
48
+ ]:
49
+ #: higher outlier_quantile makes discerning the attributions in the middle easier, but the higher attributions become the same
50
+ if outlier_quantile == 0:
51
+ max_attr = torch.max(
52
+ attributions_normalized,
53
+ dim=-1,
54
+ keepdim=True,
55
+ ).values
56
+ else:
57
+ max_attr = torch.quantile(
58
+ attributions_normalized,
59
+ 1 - outlier_quantile,
60
+ dim=-1,
61
+ keepdim=True,
62
+ )
63
+
64
+ # max_attr = torch.max(attributions_normalized).item()
65
+ else:
66
+ max_attr = None
67
+
68
+ if normalizer in [
69
+ "scale_by_max_signed_attr",
70
+ "shift_min_to_zero",
71
+ ]:
72
+ if outlier_quantile == 0:
73
+ min_attr = torch.min(
74
+ attributions_normalized,
75
+ dim=-1,
76
+ keepdim=True,
77
+ ).values
78
+ else:
79
+ min_attr = torch.quantile(
80
+ attributions_normalized,
81
+ outlier_quantile,
82
+ dim=-1,
83
+ keepdim=True,
84
+ )
85
+
86
+ # min_attr = torch.min(attributions_normalized).item()
87
+ else:
88
+ min_attr = None
89
+
90
+ if normalizer in [
91
+ "scale_by_max_abs_attr",
92
+ ]:
93
+ if outlier_quantile == 0:
94
+ max_abs_attr = torch.max(
95
+ torch.abs(attributions_normalized),
96
+ dim=-1,
97
+ keepdim=True,
98
+ ).values
99
+ else:
100
+ max_abs_attr = torch.quantile(
101
+ torch.abs(attributions_normalized),
102
+ 1 - outlier_quantile,
103
+ dim=-1,
104
+ keepdim=True,
105
+ )
106
+
107
+ # max_abs_attr = torch.max(torch.abs(attributions_normalized)).item()
108
+ else:
109
+ max_abs_attr = None
110
+
111
+ # ic(
112
+ # attributions_normalized.shape,
113
+ # max_attr.shape,
114
+ # min_attr.shape,
115
+ # max_abs_attr.shape,
116
+ # )
117
+ # attributions_normalized.shape: torch.Size([10, 196])
118
+ # max_attr.shape: torch.Size([10, 1])
119
+ # min_attr.shape: torch.Size([10, 1])
120
+ # max_abs_attr.shape: torch.Size([10, 1])
121
+
122
+ if normalizer == "scale_by_max_abs_attr":
123
+ attributions_normalized /= max_abs_attr
124
+
125
+ elif normalizer.lower() == "relu":
126
+ attributions_normalized = F.relu(attributions_normalized)
127
+
128
+ elif normalizer == "scale_by_max_signed_attr":
129
+ attributions_normalized = torch.where(
130
+ attributions_normalized < 0,
131
+ attributions_normalized / torch.abs(min_attr),
132
+ attributions_normalized / max_attr,
133
+ )
134
+
135
+ elif normalizer == "shift_min_to_zero":
136
+ attributions_normalized -= min_attr
137
+ attributions_normalized = torch.relu(attributions_normalized)
138
+
139
+ elif normalizer == "rank_uniform":
140
+ ranks_top_is_1 = rank_tensor(
141
+ attributions_normalized,
142
+ dim=-1,
143
+ descending=True,
144
+ increment=1,
145
+ )
146
+ #: The most important patch is turned into =1=, and the least important patch is turned into =len(attr)=.
147
+
148
+ attributions_normalized = 1.0 - (
149
+ (ranks_top_is_1 - 1.0) / (ranks_top_is_1.shape[-1] - 1.0)
150
+ )
151
+ #: =ranks_top_is_1 - 1= makes the ranks_top_is_1 start from zero.
152
+ #: =ranks_top_is_1.shape[-1] - 1= adjusts the denominator for the above change.
153
+ #: =1 - ...= reverses the order of the ranking, so that the first rank becomes 1, and the last rank becomes 0.
154
+
155
+ else:
156
+ raise ValueError(f"Unsupported normalizer: {normalizer}")
157
+
158
+ return simple_obj(
159
+ attributions_skipped=attributions_skipped,
160
+ attributions_normalized=attributions_normalized,
161
+ )
162
+
163
+
164
+ ##
165
+ def transform_attr_threshold(
166
+ batch,
167
+ *,
168
+ attr_name,
169
+ normalize_opts=None,
170
+ gt_threshold=None,
171
+ lt_threshold=None,
172
+ device=None,
173
+ return_mode="mutate",
174
+ ):
175
+ attr = batch[attr_name]
176
+ attr_new = attr # .clone()
177
+ #: =normalize_map= clones its input, so we don't need to do so.
178
+
179
+ if device is not None:
180
+ attr_new = attr_new.to(device)
181
+
182
+ attr_name_new = attr_name
183
+
184
+ attr_name_new = re.sub(r"^attributions_s_", "attributions_p_", attr_name_new)
185
+ #: _s stands for "signed scalar", and _p stands for "per-patch".
186
+
187
+ # ic(attr_name, attr_name_new)
188
+
189
+ if normalize_opts is not None:
190
+ normalize = normalize_opts["normalize"]
191
+ normalize = to_iterable(normalize)
192
+ attr_new = normalize_map(
193
+ attr_new,
194
+ **normalize_opts,
195
+ ).attributions_normalized
196
+ attr_name_new += f"""_N:{'->'.join(normalize)}"""
197
+
198
+ if gt_threshold is not None:
199
+ attr_name_new += f"_GT{gt_threshold}"
200
+
201
+ mask = attr_new > gt_threshold
202
+ attr_new = mask * attr_new
203
+
204
+ if lt_threshold is not None:
205
+ attr_name_new += f"_LT{lt_threshold}"
206
+
207
+ mask = attr_name < lt_threshold
208
+ attr_new = mask * attr_new
209
+
210
+ if return_mode == "mutate":
211
+ batch[attr_name_new] = attr_new
212
+ elif return_mode == "new_only":
213
+ batch = dict()
214
+ batch[attr_name_new] = attr_new
215
+ else:
216
+ raise ValueError(f"Unsupported return_mode: {return_mode}")
217
+
218
+ return TransformResult(
219
+ result=batch,
220
+ name=attr_name_new,
221
+ #: =name= is used in =segment_by_attr=.
222
+ )
223
+
224
+
225
+ ##
@@ -0,0 +1,44 @@
1
+ import base64
2
+ import tempfile
3
+ import os
4
+ from PIL import Image
5
+
6
+
7
+ ##
8
+ def base64_encode_file(file_path):
9
+ with open(file_path, "rb") as file_file:
10
+ return base64.b64encode(file_file.read()).decode("utf-8")
11
+
12
+
13
+ def convert_to_jpeg_and_base64_encode(file_path, url_p=True):
14
+ if os.path.exists(file_path):
15
+ with Image.open(file_path) as img:
16
+ if img.format.lower() not in ["jpg", "jpeg"]:
17
+ #: I don't know if =format= is normalized first or not, so I took the safest approach above.
18
+
19
+ rgb_im = img.convert(
20
+ "RGB"
21
+ ) # Convert to RGB if necessary (e.g., RGBA, P mode)
22
+
23
+ # Save the image to a temporary file
24
+ with tempfile.NamedTemporaryFile(
25
+ suffix=".jpeg", delete=True
26
+ ) as tmpfile:
27
+ temp_path = tmpfile.name
28
+ rgb_im.save(temp_path, format="JPEG")
29
+
30
+ # Use the temporary file for base64 encoding
31
+ file_base64 = base64_encode_file(temp_path)
32
+ else:
33
+ #: If the image is already a JPEG, encode it directly
34
+ file_base64 = base64_encode_file(file_path)
35
+
36
+ if url_p:
37
+ return f"data:image/jpeg;base64,{file_base64}"
38
+ else:
39
+ return file_base64
40
+ else:
41
+ raise FileNotFoundError(f"The file {file_path} does not exist.")
42
+
43
+
44
+ ##
@@ -0,0 +1,21 @@
1
+ from brish import (
2
+ z,
3
+ z_background,
4
+ )
5
+
6
+
7
+ def bello():
8
+ z("bello")
9
+
10
+
11
+ def bell_gpt():
12
+ z("bell-gpt")
13
+
14
+
15
+ def bell_call_remote(bell_name):
16
+ try:
17
+ if bell_name:
18
+ z_background("bell-call-remote {bell_name}")
19
+
20
+ except Exception as e:
21
+ print(f"Error remote-calling bell:\n bell_name: {bell_name}\n {e}")
@@ -0,0 +1,4 @@
1
+ from pynight.common_model_name import (
2
+ open_clip_sep,
3
+ #: @backcompat This used to be located here.
4
+ )
@@ -0,0 +1,34 @@
1
+ import pyperclip
2
+ import threading
3
+ import time
4
+
5
+
6
+ def clipboard_copy(
7
+ obj,
8
+ *,
9
+ ignore_errors=True,
10
+ ):
11
+ try:
12
+ return pyperclip.copy(obj)
13
+ except:
14
+ if ignore_errors:
15
+ return None
16
+ else:
17
+ raise
18
+
19
+
20
+ def clipboard_copy_multi_sync(
21
+ texts,
22
+ sleep=0.5,
23
+ ):
24
+ for text in texts:
25
+ if text: #: Check if the text is not empty or None
26
+ clipboard_copy(text)
27
+ time.sleep(sleep)
28
+ #: to allow polling-based clipboard managers to capture the text
29
+
30
+
31
+ def clipboard_copy_multi(*args):
32
+ copy_thread = threading.Thread(target=clipboard_copy_multi_sync, args=(args,))
33
+
34
+ copy_thread.start()
@@ -3,6 +3,7 @@ import numpy
3
3
 
4
4
  np = numpy
5
5
 
6
+
6
7
  ##
7
8
  def partition_int_into_fixed_gen(n, boxes, min=1):
8
9
  "Partitions the integer 'n' into 'boxes' boxes, each box having at least 'min' elements. Order matters. Returns a generator."
@@ -1,24 +1,32 @@
1
1
  import os
2
2
  from os import getenv
3
3
 
4
+
4
5
  ##
5
6
  def jupyter_p():
6
7
  return jupyter_p_v1() or jupyter_p_v2()
7
-
8
+
9
+
8
10
  def jupyter_p_v1():
9
- return getenv('INSIDE_JUPYTER_NOTEBOOK_P', default='') == 'y'
11
+ return getenv("INSIDE_JUPYTER_NOTEBOOK_P", default="") == "y"
12
+
10
13
 
11
14
  def jupyter_p_v2():
12
15
  #: [[https://stackoverflow.com/questions/15411967/how-can-i-check-if-code-is-executed-in-the-ipython-notebook][python - How can I check if code is executed in the IPython notebook? - Stack Overflow]]
13
16
  ##
14
17
  try:
15
18
  shell = get_ipython().__class__.__name__
16
- if shell == 'ZMQInteractiveShell':
17
- return True # Jupyter notebook or qtconsole
18
- elif shell == 'TerminalInteractiveShell':
19
+ if shell == "ZMQInteractiveShell":
20
+ return True # Jupyter notebook or qtconsole
21
+
22
+ elif shell == "TerminalInteractiveShell":
19
23
  return False # Terminal running IPython
24
+
20
25
  else:
21
26
  return False # Other type (?)
27
+
22
28
  except NameError:
23
- return False # Probably standard Python interpreter
29
+ return False # Probably standard Python interpreter
30
+
31
+
24
32
  ##
@@ -34,4 +34,5 @@ def dict_to_csv(data, header_p=True):
34
34
 
35
35
  return csv_string
36
36
 
37
+
37
38
  ##