lkj 0.1.47__py3-none-any.whl → 0.1.49__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.
lkj/__init__.py CHANGED
@@ -15,7 +15,12 @@ from lkj.dicts import (
15
15
  merge_dicts, # Merge multiple dictionaries recursively
16
16
  compare_field_values, # Compare two dictionaries' values
17
17
  )
18
- from lkj.filesys import get_app_data_dir, get_watermarked_dir, enable_sourcing_from_file
18
+ from lkj.filesys import (
19
+ get_app_data_dir,
20
+ get_watermarked_dir,
21
+ enable_sourcing_from_file,
22
+ search_folder_fast, # Fast search for a term in files under a folder (uses rg command)
23
+ )
19
24
  from lkj.strings import (
20
25
  print_list, # Print a list in a nice format (or get a string to process yourself)
21
26
  FindReplaceTool, # Tool for finding and replacing substrings in a string
lkj/chunking.py CHANGED
@@ -3,29 +3,26 @@
3
3
  from itertools import zip_longest, chain, islice
4
4
 
5
5
  from typing import (
6
- Iterable,
7
6
  Union,
8
7
  Dict,
9
8
  List,
10
9
  Tuple,
11
- Mapping,
12
10
  TypeVar,
13
- Iterator,
14
- Callable,
15
11
  Optional,
16
12
  T,
17
13
  )
14
+ from collections.abc import Iterable, Mapping, Iterator, Callable
18
15
 
19
16
  KT = TypeVar("KT") # there's a typing.KT, but pylance won't allow me to use it!
20
17
  VT = TypeVar("VT") # there's a typing.VT, but pylance won't allow me to use it!
21
18
 
22
19
 
23
20
  def chunk_iterable(
24
- iterable: Union[Iterable[T], Mapping[KT, VT]],
21
+ iterable: Iterable[T] | Mapping[KT, VT],
25
22
  chk_size: int,
26
23
  *,
27
- chunk_type: Optional[Callable[..., Union[Iterable[T], Mapping[KT, VT]]]] = None,
28
- ) -> Iterator[Union[List[T], Tuple[T, ...], Dict[KT, VT]]]:
24
+ chunk_type: Callable[..., Iterable[T] | Mapping[KT, VT]] | None = None,
25
+ ) -> Iterator[list[T] | tuple[T, ...] | dict[KT, VT]]:
29
26
  """
30
27
  Divide an iterable into chunks/batches of a specific size.
31
28
 
@@ -78,7 +75,7 @@ def chunk_iterable(
78
75
 
79
76
  def chunker(
80
77
  a: Iterable[T], chk_size: int, *, include_tail: bool = True
81
- ) -> Iterator[Tuple[T, ...]]:
78
+ ) -> Iterator[tuple[T, ...]]:
82
79
  """
83
80
  Chunks an iterable into non-overlapping chunks of size `chk_size`.
84
81
 
lkj/dicts.py CHANGED
@@ -45,20 +45,24 @@ def exclusive_subdict(d, exclude):
45
45
  def truncate_dict_values(
46
46
  d: dict,
47
47
  *,
48
- max_list_size: Optional[int] = 2,
49
- max_string_size: Optional[int] = 66,
48
+ max_list_size: int | None = 2,
49
+ max_string_size: int | None = 66,
50
50
  middle_marker: str = "...",
51
+ d_ingress: callable = lambda d: d,
51
52
  ) -> dict:
52
53
  """
53
54
  Returns a new dictionary with the same nested keys structure, where:
54
55
  - List values are reduced to a maximum size of max_list_size.
55
56
  - String values longer than max_string_size are truncated in the middle.
57
+ - Values are preprocessed with d_ingress before type checking.
56
58
 
57
59
  Parameters:
58
60
  d (dict): The input dictionary.
59
61
  max_list_size (int, optional): Maximum size for lists. Defaults to 2.
60
62
  max_string_size (int, optional): Maximum length for strings. Defaults to None (no truncation).
61
63
  middle_marker (str, optional): String to insert in the middle of truncated strings. Defaults to '...'.
64
+ d_ingress (callable, optional): Function applied to each value before type checking.
65
+ Defaults to identity function. Useful for handling special types like pandas Series.
62
66
 
63
67
  Returns:
64
68
  dict: A new dictionary with truncated lists and strings.
@@ -79,6 +83,14 @@ def truncate_dict_values(
79
83
  ... == large_dict
80
84
  ... )
81
85
 
86
+ For handling special types like pandas Series:
87
+
88
+ >>> def handle_pandas(obj):
89
+ ... if hasattr(obj, 'head'): # pandas Series/DataFrame
90
+ ... return f"<{type(obj).__name__}: {len(obj)} items>"
91
+ ... return obj
92
+ >>> # truncate_dict_values(data_with_pandas, d_ingress=handle_pandas)
93
+
82
94
  """
83
95
 
84
96
  def truncate_string(value, max_len, marker):
@@ -87,26 +99,34 @@ def truncate_dict_values(
87
99
  half_len = (max_len - len(marker)) // 2
88
100
  return value[:half_len] + marker + value[-half_len:]
89
101
 
102
+ # Apply d_ingress to preprocess the value
103
+ d = d_ingress(d)
104
+
90
105
  kwargs = dict(
91
106
  max_list_size=max_list_size,
92
107
  max_string_size=max_string_size,
93
108
  middle_marker=middle_marker,
109
+ d_ingress=d_ingress,
94
110
  )
95
111
  if isinstance(d, dict):
96
112
  return {k: truncate_dict_values(v, **kwargs) for k, v in d.items()}
97
- elif isinstance(d, list):
98
- return (
99
- [truncate_dict_values(v, **kwargs) for v in d[:max_list_size]]
100
- if max_list_size is not None
101
- else d
102
- )
113
+ elif isinstance(d, (list, tuple)):
114
+ container_type = type(d)
115
+ if max_list_size is not None:
116
+ truncated_items = [
117
+ truncate_dict_values(v, **kwargs) for v in d[:max_list_size]
118
+ ]
119
+ else:
120
+ truncated_items = [truncate_dict_values(v, **kwargs) for v in d]
121
+ return container_type(truncated_items)
103
122
  elif isinstance(d, str):
104
123
  return truncate_string(d, max_string_size, middle_marker)
105
124
  else:
106
125
  return d
107
126
 
108
127
 
109
- from typing import Mapping, Callable, TypeVar, Iterable, Tuple
128
+ from typing import TypeVar, Tuple
129
+ from collections.abc import Mapping, Callable, Iterable
110
130
 
111
131
  KT = TypeVar("KT") # Key type
112
132
  VT = TypeVar("VT") # Value type
@@ -119,7 +139,7 @@ def merge_dicts(
119
139
  *mappings: Mapping[KT, VT],
120
140
  recursive_condition: Callable[[VT], bool] = lambda v: isinstance(v, Mapping),
121
141
  conflict_resolver: Callable[[VT, VT], VT] = lambda x, y: y,
122
- mapping_constructor: Callable[[Iterable[Tuple[KT, VT]]], Mapping[KT, VT]] = dict,
142
+ mapping_constructor: Callable[[Iterable[tuple[KT, VT]]], Mapping[KT, VT]] = dict,
123
143
  ) -> Mapping[KT, VT]:
124
144
  """
125
145
  Merge multiple mappings into a single mapping, recursively if needed,
@@ -218,7 +238,8 @@ def merge_dicts(
218
238
 
219
239
 
220
240
  import operator
221
- from typing import Callable, Dict, Any
241
+ from typing import Dict, Any
242
+ from collections.abc import Callable
222
243
 
223
244
  Comparison = Any
224
245
  Comparator = Callable[[dict, dict], Comparison]
@@ -232,10 +253,10 @@ def compare_field_values(
232
253
  dict1,
233
254
  dict2,
234
255
  *,
235
- field_comparators: Dict[KT, Comparator] = {},
256
+ field_comparators: dict[KT, Comparator] = {},
236
257
  default_comparator: Comparator = operator.eq,
237
258
  aggregator: Callable[
238
- [Dict[KT, Comparison]], Any
259
+ [dict[KT, Comparison]], Any
239
260
  ] = lambda d: d, # lambda d: np.mean(list(d.values())),
240
261
  get_comparison_fields: Callable[[dict], Iterable[KT]] = _common_keys_list,
241
262
  ):
lkj/filesys.py CHANGED
@@ -1,7 +1,160 @@
1
1
  """File system utils"""
2
2
 
3
+ # -------------------------------------------------------------------------------------
4
+ # Search (on linux and macOS) using rg (ripgrep)
5
+ import subprocess
6
+ import json
7
+ import shutil
8
+ from typing import Callable, Any, Optional
9
+
10
+ # simple parser that returns list of dicts objects from a JSONL string
11
+ simple_jsonl_parser = lambda string: list(map(json.loads, string.splitlines()))
12
+
13
+ # --- Default Egress Function ---
14
+
15
+
16
+ def _ripgrep_json_parser(rg_output: str) -> list[dict]:
17
+ """
18
+ Parses the line-by-line JSON stream output from ripgrep (rg --json).
19
+
20
+ This output is NOT a single valid JSON object, but a stream of JSON lines.
21
+ We only care about 'match' entries for the results.
22
+ """
23
+ results = []
24
+
25
+ # ripgrep outputs one JSON object per line, so we iterate line by line
26
+ for line in rg_output.strip().split("\n"):
27
+ if not line:
28
+ continue
29
+
30
+ try:
31
+ data = json.loads(line)
32
+ except json.JSONDecodeError:
33
+ # Skip any lines that aren't valid JSON (shouldn't happen with --json)
34
+ continue
35
+
36
+ if data.get("type") == "match":
37
+ match_data = data.get("data", {})
38
+
39
+ # Extract key information from the match
40
+ result = {
41
+ "path": match_data.get("path", {}).get("text"),
42
+ "line_number": match_data.get("line_number"),
43
+ # The text of the matched line, decoded
44
+ "line_text": match_data.get("lines", {}).get("text"),
45
+ "submatches": [
46
+ {
47
+ "match_text": sub.get("match", {}).get("text"),
48
+ "start": sub.get("start"),
49
+ "end": sub.get("end"),
50
+ }
51
+ for sub in match_data.get("submatches", [])
52
+ ],
53
+ }
54
+ results.append(result)
55
+
56
+ return results
57
+
58
+
59
+ def search_folder_fast(
60
+ search_term: str,
61
+ path_to_search: str = ".",
62
+ *, # Enforce 'egress' as a keyword-only argument
63
+ egress: Optional[Callable[[str], Any]] = _ripgrep_json_parser,
64
+ ) -> Any:
65
+ """
66
+ Executes a fast, recursive search using ripgrep and processes the results.
67
+
68
+ :param search_term: The regex pattern or text string to search for.
69
+ :param path_to_search: The folder path to start searching from. Defaults to current directory.
70
+ :param egress: A callable function to process the raw ripgrep output string.
71
+ Defaults to a parser that returns a list of dictionaries for matches.
72
+ If set to None, it defaults to a lambda returning the raw output (string).
73
+ You can also give it simple_jsonl_parser to get a list of all JSON objects in the output.
74
+ :return: The output of the 'egress' function.
75
+
76
+ Example usage:
77
+ -------------
78
+ >>> results = search_folder_fast("my_function_name", path_to_search='/path/to/project') # doctest: +SKIP
79
+ >>> for match in results: # doctest: +SKIP
80
+ ... print(f"Found in {match['path']} at line {match['line_number']}: {match['line_text']}") # doctest: +SKIP
81
+
82
+
83
+ """
84
+
85
+ if shutil.which("rg") is None:
86
+ print("=" * 60)
87
+ print(
88
+ "🚨 Error: The 'rg' (ripgrep) command was not found in your system's PATH."
89
+ )
90
+ print("\nTo install ripgrep:")
91
+ print(" - Linux (Debian/Ubuntu): sudo apt install ripgrep")
92
+ print(" - macOS (Homebrew): brew install ripgrep")
93
+ print(" - Windows (Chocolatey): choco install ripgrep")
94
+ print(f"\nMore details: https://github.com/BurntSushi/ripgrep#installation")
95
+ print("=" * 60)
96
+ return None
97
+
98
+ # Standard ripgrep command with JSON output
99
+ # -i: case-insensitive, -r: recursive, -n: show line numbers
100
+ # --json: outputs machine-readable JSON format
101
+ # --color never: ensures no ANSI color codes in the output stream
102
+ command = [
103
+ "rg",
104
+ "-i",
105
+ "-r",
106
+ "-n",
107
+ "--json",
108
+ "--color",
109
+ "never",
110
+ search_term,
111
+ path_to_search,
112
+ ]
113
+
114
+ # Handle the default None case for egress (return raw string)
115
+ if egress is None:
116
+ egress = lambda x: x
117
+
118
+ try:
119
+ # Use text=True and capture_output=True for string output and capturing stdout/stderr
120
+ result = subprocess.run(command, capture_output=True, text=True, check=True)
121
+
122
+ # Call the egress function on the raw standard output
123
+ return egress(result.stdout)
124
+
125
+ except subprocess.CalledProcessError as e:
126
+ # ripgrep returns a non-zero code if no matches are found.
127
+ # This is expected behavior and should not be treated as an error
128
+ # unless stderr indicates a true issue.
129
+ # Check stderr for real errors
130
+ if e.stderr:
131
+ # Handle genuine errors like permissions or file issues
132
+ print(f"A genuine ripgrep error occurred: {e.stderr.strip()}")
133
+ return None
134
+
135
+ # If check=True and return code is 1, it usually means "no matches found."
136
+ # If there's no stderr, return the egress of an empty string (or handle as no results)
137
+ return egress("")
138
+
139
+ except FileNotFoundError:
140
+ print(
141
+ "Error: ripgrep ('rg') command not found. Please ensure it is installed and in your PATH."
142
+ )
143
+ return None
144
+
145
+
146
+ # Example usage will require 'rg' installed and a file system to search
147
+ # For demonstration purposes, assume 'rg' is installed and you are searching for 'wrap_kvs'
148
+ # search_results = search_folder_fast("wrap_kvs", path_to_search='/path/to/your/project')
149
+ #
150
+ # print(search_results)
151
+
152
+ # -------------------------------------------------------------------------------------
153
+ # General utils for file system operations
154
+
3
155
  import os
4
- from typing import Callable, Any
156
+ from typing import Any
157
+ from collections.abc import Callable
5
158
  from pathlib import Path
6
159
  from functools import wraps, partial
7
160
 
@@ -32,7 +185,7 @@ def enable_sourcing_from_file(func=None, *, write_output=False):
32
185
  if args and isinstance(args[0], str) and os.path.isfile(args[0]):
33
186
  file_path = args[0]
34
187
  # Read the file content
35
- with open(file_path, "r") as file:
188
+ with open(file_path) as file:
36
189
  file_content = file.read()
37
190
  # Call the function with the file content and other arguments
38
191
  new_args = (file_content,) + args[1:]
lkj/iterables.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """Tools with iterables (dicts, lists, tuples, sets, etc.)."""
2
2
 
3
- from typing import Sequence, Mapping, KT, VT, Iterable, Iterable, NamedTuple
3
+ from typing import KT, VT, NamedTuple
4
+ from collections.abc import Sequence, Mapping, Iterable, Iterable
4
5
 
5
6
 
6
7
  class SetsComparisonResult(NamedTuple):
lkj/loggers.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """Utils for logging."""
2
2
 
3
- from typing import Callable, Tuple, Any, Optional, Union, Iterable
3
+ from typing import Tuple, Any, Optional, Union
4
+ from collections.abc import Callable, Iterable
4
5
  from functools import partial, wraps
5
6
  from operator import attrgetter
6
7
 
@@ -9,7 +10,7 @@ from operator import attrgetter
9
10
  # TODO: Merge with wrap_text_with_exact_spacing
10
11
  # TODO: Add doctests for string
11
12
  def wrapped_print(
12
- items: Union[str, Iterable],
13
+ items: str | Iterable,
13
14
  sep=", ",
14
15
  max_width=80,
15
16
  *,
@@ -166,15 +167,15 @@ def clog(condition, *args, log_func=print, **kwargs):
166
167
  return log_func(*args, **kwargs)
167
168
 
168
169
 
169
- def _calling_name(func_name: str, args: Tuple, kwargs: dict) -> str:
170
+ def _calling_name(func_name: str, args: tuple, kwargs: dict) -> str:
170
171
  return f"Calling {func_name}..."
171
172
 
172
173
 
173
- def _done_calling_name(func_name: str, args: Tuple, kwargs: dict, result: Any) -> str:
174
+ def _done_calling_name(func_name: str, args: tuple, kwargs: dict, result: Any) -> str:
174
175
  return f".... Done calling {func_name}"
175
176
 
176
177
 
177
- def _always_log(func: Callable, args: Tuple, kwargs: dict) -> bool:
178
+ def _always_log(func: Callable, args: tuple, kwargs: dict) -> bool:
178
179
  """Return True no matter what"""
179
180
  return True
180
181
 
@@ -183,10 +184,10 @@ def log_calls(
183
184
  func: Callable = None,
184
185
  *,
185
186
  logger: Callable[[str], None] = print,
186
- ingress_msg: Callable[[str, Tuple, dict], str] = _calling_name,
187
- egress_msg: Callable[[str, Tuple, dict, Any], str] = _done_calling_name,
187
+ ingress_msg: Callable[[str, tuple, dict], str] = _calling_name,
188
+ egress_msg: Callable[[str, tuple, dict, Any], str] = _done_calling_name,
188
189
  func_name: Callable[[Callable], str] = attrgetter("__name__"),
189
- log_condition: Callable[[Callable, Tuple, dict], bool] = _always_log,
190
+ log_condition: Callable[[Callable, tuple, dict], bool] = _always_log,
190
191
  ) -> Callable:
191
192
  """
192
193
  Decorator that adds logging before and after the function's call.
@@ -337,10 +338,12 @@ log_calls.instance_flag_is_set = instance_flag_is_set
337
338
  # Error handling
338
339
 
339
340
 
340
- from typing import Callable, Tuple, Any
341
+ from typing import Tuple, Any
342
+ from collections.abc import Callable
341
343
  from dataclasses import dataclass
342
344
  import traceback
343
- from typing import Callable, Any, Tuple
345
+ from typing import Any, Tuple
346
+ from collections.abc import Callable
344
347
  from functools import partial, wraps
345
348
  from operator import attrgetter
346
349
 
@@ -371,7 +374,7 @@ def dflt_error_info_processor(
371
374
  def return_error_info_on_error(
372
375
  func,
373
376
  *,
374
- caught_error_types: Tuple[Exception] = (Exception,),
377
+ caught_error_types: tuple[Exception] = (Exception,),
375
378
  error_info_processor: Callable[[ErrorInfo], Any] = dflt_error_info_processor,
376
379
  ):
377
380
  """Decorator that returns traceback and local variables on error.
lkj/misc.py CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  from operator import attrgetter, ge, gt, le, lt
4
4
  from functools import partial
5
- from typing import Callable, T, Optional, Any
5
+ from typing import T, Optional, Any
6
+ from collections.abc import Callable
6
7
 
7
8
 
8
9
  def identity(x):
@@ -14,8 +15,8 @@ def value_in_interval(
14
15
  /,
15
16
  *,
16
17
  get_val: Callable[[Any], T] = identity,
17
- min_val: Optional[T] = None,
18
- max_val: Optional[T] = None,
18
+ min_val: T | None = None,
19
+ max_val: T | None = None,
19
20
  is_minimum: Callable[[T, T], bool] = ge,
20
21
  is_maximum: Callable[[T, T], bool] = lt,
21
22
  ):
lkj/strings.py CHANGED
@@ -22,7 +22,8 @@ These utilities are designed to make it easier to display, format, and manipulat
22
22
  """
23
23
 
24
24
  import re
25
- from typing import Iterable, Sequence, Callable, Optional, Any, Literal
25
+ from typing import Optional, Any, Literal
26
+ from collections.abc import Iterable, Sequence, Callable
26
27
  from functools import partial
27
28
 
28
29
 
@@ -327,7 +328,7 @@ def regex_based_substitution(replacements: dict, regex=None, s: str = None):
327
328
  )
328
329
 
329
330
 
330
- from typing import Callable, Iterable, Sequence
331
+ from collections.abc import Callable, Iterable, Sequence
331
332
 
332
333
 
333
334
  class TrieNode:
@@ -453,12 +454,13 @@ def unique_affixes(
453
454
  return affixes
454
455
 
455
456
 
456
- from typing import Union, Callable, Dict, Any
457
+ from typing import Union, Dict, Any
458
+ from collections.abc import Callable
457
459
 
458
460
  # A match is represented as a dictionary (keys like "start", "end", etc.)
459
461
  # and the replacement is either a static string or a callable that takes that
460
462
  # dictionary and returns a string.
461
- Replacement = Union[str, Callable[[Dict[str, Any]], str]]
463
+ Replacement = Union[str, Callable[[dict[str, Any]], str]]
462
464
 
463
465
 
464
466
  class FindReplaceTool:
@@ -770,7 +772,7 @@ class FindReplaceTool:
770
772
 
771
773
 
772
774
  def print_list(
773
- items: Optional[Iterable[Any]] = None,
775
+ items: Iterable[Any] | None = None,
774
776
  *,
775
777
  style: Literal[
776
778
  "wrapped", "columns", "numbered", "bullet", "table", "compact"
@@ -779,7 +781,7 @@ def print_list(
779
781
  sep: str = ", ",
780
782
  line_prefix: str = "",
781
783
  items_per_line=None,
782
- show_count: Union[bool, Callable[[int], str]] = False,
784
+ show_count: bool | Callable[[int], str] = False,
783
785
  title=None,
784
786
  print_func=print,
785
787
  ):
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: lkj
3
- Version: 0.1.47
3
+ Version: 0.1.49
4
4
  Summary: A dump of homeless useful utils
5
5
  Home-page: https://github.com/thorwhalen/lkj
6
6
  Author: Thor Whalen
@@ -8,6 +8,8 @@ License: apache-2.0
8
8
  Platform: any
9
9
  Description-Content-Type: text/markdown
10
10
  License-File: LICENSE
11
+ Provides-Extra: testing
12
+ Dynamic: license-file
11
13
 
12
14
  # lkj
13
15
 
@@ -0,0 +1,17 @@
1
+ lkj/__init__.py,sha256=z8IZi5ql-bCM0xRgcEERg48oi2SRkgX9qSZdyaKb62Y,7300
2
+ lkj/chunking.py,sha256=K7yakDuTgyn6t2rvFeDIWS5pO636BKGOe1spKk7v4Mc,3746
3
+ lkj/dicts.py,sha256=67E9qwxPLLsBeRj67cSDgkAGvB6xfmNsi8igXdFGwjA,11307
4
+ lkj/filesys.py,sha256=hYWztDoJUvVcPMD5AxRyYKkTB-sy5hx6_RDw4OyRcos,14628
5
+ lkj/funcs.py,sha256=LXJlj3gMMsbD0t2gn2NZZ6mOqmW5bxM-94uGoYgrhzI,8930
6
+ lkj/importing.py,sha256=TcW3qUDmw7jqswpxXnksjlHkkbOJq70NbUk1ZyaafT0,6658
7
+ lkj/iterables.py,sha256=JSuTm7XpaTjIlv_9XZTrApLBohu5hRu7ZHi0wDT_ky0,2828
8
+ lkj/loggers.py,sha256=eCudnqFaQhWz95qZtB57DDhvhXYvwl1JVCVernMhE5c,13781
9
+ lkj/misc.py,sha256=RqJg4HgcT1ZZRBtxOppwYibvvLIyn6pNqI-WMg0uKgI,1457
10
+ lkj/strings.py,sha256=JukQ4pvLlwGajBEK0PXyqsumEFKm8i1rx6SgASz7tqo,43798
11
+ lkj/tests/__init__.py,sha256=kReYfWiyz1T79AuZAy5m4PIBj3Oj_Dlc0E-T8HVQ504,20
12
+ lkj/tests/test_strings.py,sha256=Ix54io8WqWmYTY4guBgZShtKabtjd5uVpdxzxwJkgNA,1707
13
+ lkj-0.1.49.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
14
+ lkj-0.1.49.dist-info/METADATA,sha256=pURwBZHm355AXQBzLViKArzBWbJhCFLxiNiHRaNRih8,8267
15
+ lkj-0.1.49.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
16
+ lkj-0.1.49.dist-info/top_level.txt,sha256=3DZOUwYmyurJFBXQCvCmEIVm8z2b42O5Sx3RDQyePfg,4
17
+ lkj-0.1.49.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.45.1)
2
+ Generator: setuptools (79.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,17 +0,0 @@
1
- lkj/__init__.py,sha256=IbBzGqZp9oXRt5oFyZwRFh9MHcFy8HW30S7Ox15RxEs,7191
2
- lkj/chunking.py,sha256=RpNdx5jEuO4mFg2qoTkD47iL35neBneuZ5xgQ_cBkiM,3755
3
- lkj/dicts.py,sha256=z2o7njvLNJkh1ZgSH-SLtz13SdW_YfUsTA1yTY-kVLE,10382
4
- lkj/filesys.py,sha256=NbWDuc848h8O42gwX7d9yNJkrWBgzSFnkoEdSRgvBAg,8883
5
- lkj/funcs.py,sha256=LXJlj3gMMsbD0t2gn2NZZ6mOqmW5bxM-94uGoYgrhzI,8930
6
- lkj/importing.py,sha256=TcW3qUDmw7jqswpxXnksjlHkkbOJq70NbUk1ZyaafT0,6658
7
- lkj/iterables.py,sha256=9jeO36w-IGiZryge7JKgXZOGZAgehUvhwKV3nHPcZWk,2801
8
- lkj/loggers.py,sha256=ImmBdacz89Lvb3dg_xI5jOct_44rSRv0hNI_CVehy60,13706
9
- lkj/misc.py,sha256=IZf05tkl0cgiMgBwCMH5cLSC59fRXwnemPRo8G0OxQg,1436
10
- lkj/strings.py,sha256=eFPA1EzEyLaF8wPk0Srj8rzrdEN9GNXc03wfvV4MOGU,43744
11
- lkj/tests/__init__.py,sha256=kReYfWiyz1T79AuZAy5m4PIBj3Oj_Dlc0E-T8HVQ504,20
12
- lkj/tests/test_strings.py,sha256=Ix54io8WqWmYTY4guBgZShtKabtjd5uVpdxzxwJkgNA,1707
13
- lkj-0.1.47.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
14
- lkj-0.1.47.dist-info/METADATA,sha256=bpcIVJxkJl8wIa2ycVWRmbhVJ3-o-5y-S46A3wepHeo,8221
15
- lkj-0.1.47.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
16
- lkj-0.1.47.dist-info/top_level.txt,sha256=3DZOUwYmyurJFBXQCvCmEIVm8z2b42O5Sx3RDQyePfg,4
17
- lkj-0.1.47.dist-info/RECORD,,