mayutils 1.2.25__tar.gz → 1.2.28__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 (66) hide show
  1. {mayutils-1.2.25 → mayutils-1.2.28}/PKG-INFO +11 -1
  2. {mayutils-1.2.25 → mayutils-1.2.28}/pyproject.toml +29 -1
  3. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/data/live.py +15 -4
  4. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/data/queries/__init__.py +7 -2
  5. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/data/read.py +6 -2
  6. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/environment/logging.py +3 -1
  7. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/environment/memoisation.py +4 -1
  8. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/export/slides.py +8 -2
  9. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/objects/dataframes.py +30 -10
  10. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/objects/datetime.py +6 -2
  11. mayutils-1.2.28/src/mayutils/objects/strings.py +61 -0
  12. mayutils-1.2.28/src/mayutils/visualisation/graphs/plotly/__init__.py +0 -0
  13. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/visualisation/graphs/plotly/charts.py +3 -3
  14. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/visualisation/graphs/plotly/traces.py +67 -21
  15. mayutils-1.2.25/src/mayutils/objects/strings.py +0 -7
  16. {mayutils-1.2.25 → mayutils-1.2.28}/LICENSE +0 -0
  17. {mayutils-1.2.25 → mayutils-1.2.28}/README.md +0 -0
  18. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/__init__.py +0 -0
  19. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/core/__init__.py +0 -0
  20. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/core/constants.py +0 -0
  21. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/data/__init__.py +0 -0
  22. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/data/analysis/__init__.py +0 -0
  23. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/data/cache/.gitkeep +0 -0
  24. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/data/local.py +0 -0
  25. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/data/queries/.gitkeep +0 -0
  26. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/data/queries/examples/.gitkeep +0 -0
  27. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/environment/__init__.py +0 -0
  28. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/environment/benchmarking.py +0 -0
  29. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/environment/databases.py +0 -0
  30. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/environment/filesystem.py +0 -0
  31. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/environment/oauth.py +0 -0
  32. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/environment/secrets.py +0 -0
  33. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/environment/webdrivers.py +0 -0
  34. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/export/__init__.py +0 -0
  35. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/export/images.py +0 -0
  36. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/export/pdf.py +0 -0
  37. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/interfaces/__init__.py +0 -0
  38. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/interfaces/google.py +0 -0
  39. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/interfaces/microsoft.py +0 -0
  40. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/interfaces/streamlit.py +0 -0
  41. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/mathematics/__init__.py +0 -0
  42. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/mathematics/numba.py +0 -0
  43. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/mathematics/numpy.py +0 -0
  44. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/objects/__init__.py +0 -0
  45. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/objects/classes.py +0 -0
  46. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/objects/colours.py +0 -0
  47. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/objects/decorators.py +0 -0
  48. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/objects/dictionaries.py +0 -0
  49. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/objects/functions.py +0 -0
  50. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/objects/hashing.py +0 -0
  51. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/objects/numbers.py +0 -0
  52. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/objects/types.py +0 -0
  53. /mayutils-1.2.25/src/mayutils/scripts/__init__.py → /mayutils-1.2.28/src/mayutils/py.typed +0 -0
  54. {mayutils-1.2.25/src/mayutils/testing → mayutils-1.2.28/src/mayutils/scripts}/__init__.py +0 -0
  55. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/scripts/clear_cache.py +0 -0
  56. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/scripts/versioning.py +0 -0
  57. {mayutils-1.2.25/src/mayutils/visualisation → mayutils-1.2.28/src/mayutils/testing}/__init__.py +0 -0
  58. {mayutils-1.2.25/src/mayutils/visualisation/graphs/matplotlib → mayutils-1.2.28/src/mayutils/visualisation}/__init__.py +0 -0
  59. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/visualisation/console.py +0 -0
  60. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/visualisation/graphs/__init__.py +0 -0
  61. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/visualisation/graphs/combine.py +0 -0
  62. {mayutils-1.2.25/src/mayutils/visualisation/graphs/plotly → mayutils-1.2.28/src/mayutils/visualisation/graphs/matplotlib}/__init__.py +0 -0
  63. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/visualisation/graphs/matplotlib/template.py +0 -0
  64. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/visualisation/graphs/plotly/templates.py +0 -0
  65. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/visualisation/graphs/plotly/utilities.py +0 -0
  66. {mayutils-1.2.25 → mayutils-1.2.28}/src/mayutils/visualisation/notebook.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: mayutils
3
- Version: 1.2.25
3
+ Version: 1.2.28
4
4
  Summary: Utilities for Python from Mayuran Visakan
5
5
  Author: Mayuran Visakan
6
6
  Author-email: Mayuran Visakan <mayuran.k.v@gmail.com>
@@ -70,13 +70,23 @@ Requires-Dist: requests>=2.32.5
70
70
  Requires-Dist: rich>=14.1.0
71
71
  Requires-Dist: scikit-learn>=1.7.1
72
72
  Requires-Dist: scipy>=1.16.1
73
+ Requires-Dist: scipy-stubs>=1.16.2.4
73
74
  Requires-Dist: selenium>=4.35.0
74
75
  Requires-Dist: snowflake-connector-python[secure-local-storage]>=3.17.2
75
76
  Requires-Dist: snowflake-sqlalchemy>=1.7.6
76
77
  Requires-Dist: sqlalchemy>=2.0.43
77
78
  Requires-Dist: streamlit>=1.49.0
78
79
  Requires-Dist: typer
80
+ Requires-Dist: types-cachetools>=6.2.0.20251022
81
+ Requires-Dist: types-decorator>=5.2.0.20250324
82
+ Requires-Dist: types-openpyxl>=3.1.5.20250919
83
+ Requires-Dist: types-pycurl>=7.45.7.20250926
84
+ Requires-Dist: types-python-dateutil>=2.9.0.20251008
85
+ Requires-Dist: types-pyyaml>=6.0.12.20250915
86
+ Requires-Dist: types-requests>=2.32.4.20250913
87
+ Requires-Dist: types-simplejson>=3.20.0.20250822
79
88
  Requires-Dist: types-six>=1.17.0.20250515
89
+ Requires-Dist: types-toml>=0.10.8.20240310
80
90
  Requires-Dist: unicodeit>=0.7.5
81
91
  Requires-Dist: watchdog>=6.0.0
82
92
  Requires-Dist: coverage ; extra == 'dev'
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mayutils"
3
- version = "1.2.25"
3
+ version = "1.2.28"
4
4
  description = "Utilities for Python from Mayuran Visakan"
5
5
  authors = [
6
6
  { name = "Mayuran Visakan", email = "mayuran.k.v@gmail.com" }
@@ -57,13 +57,23 @@ dependencies = [
57
57
  "rich>=14.1.0",
58
58
  "scikit-learn>=1.7.1",
59
59
  "scipy>=1.16.1",
60
+ "scipy-stubs>=1.16.2.4",
60
61
  "selenium>=4.35.0",
61
62
  "snowflake-connector-python[secure-local-storage]>=3.17.2",
62
63
  "snowflake-sqlalchemy>=1.7.6",
63
64
  "sqlalchemy>=2.0.43",
64
65
  "streamlit>=1.49.0",
65
66
  "typer",
67
+ "types-cachetools>=6.2.0.20251022",
68
+ "types-decorator>=5.2.0.20250324",
69
+ "types-openpyxl>=3.1.5.20250919",
70
+ "types-pycurl>=7.45.7.20250926",
71
+ "types-python-dateutil>=2.9.0.20251008",
72
+ "types-pyyaml>=6.0.12.20250915",
73
+ "types-requests>=2.32.4.20250913",
74
+ "types-simplejson>=3.20.0.20250822",
66
75
  "types-six>=1.17.0.20250515",
76
+ "types-toml>=0.10.8.20240310",
67
77
  "unicodeit>=0.7.5",
68
78
  "watchdog>=6.0.0",
69
79
  ]
@@ -85,10 +95,28 @@ generate_encryption_key = "mayutils.environment.oauth:generate_fernet_key"
85
95
  [tool.uv]
86
96
  prerelease = "if-necessary-or-explicit"
87
97
 
98
+ [tool.uv.sources]
99
+ mayutils = { workspace = true }
100
+
88
101
  [tool.pyright]
89
102
  venvPath = "."
90
103
  venv = ".venv"
91
104
 
105
+ [tool.mypy]
106
+ explicit_package_bases = true
107
+
108
+
92
109
  [build-system]
93
110
  requires = ["uv_build>=0.8.14,<0.9.0"]
94
111
  build-backend = "uv_build"
112
+
113
+ [dependency-groups]
114
+ dev = [
115
+ "coverage>=7.11.0",
116
+ "ipdb>=0.13.13",
117
+ "mayutils",
118
+ "mypy>=1.18.2",
119
+ "pytest>=8.4.2",
120
+ "ruff>=0.14.2",
121
+ "ty>=0.0.1a24",
122
+ ]
@@ -92,13 +92,17 @@ class LiveData(object):
92
92
  engine: EngineWrapper,
93
93
  ) -> None:
94
94
  new_interval = Interval(
95
- start=(now - self.interval.as_duration()) if self.rolling else self.interval.start,
95
+ start=(now - self.interval.as_duration())
96
+ if self.rolling
97
+ else self.interval.start,
96
98
  end=now,
97
99
  )
98
100
 
99
101
  if self.rolling:
100
102
  # elapsed_period = (previous_period[0], self.period[0])
101
- self.data = self.data.loc[self.data[self.index_column] >= new_interval.start.naive()]
103
+ self.data = self.data.loc[
104
+ self.data[self.index_column] >= new_interval.start.naive()
105
+ ]
102
106
 
103
107
  # new_period = (previous_period[1], self.period[1])»
104
108
  additional_data = engine.read_pandas(
@@ -132,7 +136,11 @@ class LiveData(object):
132
136
  if engine is None:
133
137
  engine = self.engine
134
138
 
135
- if force or self.update_frequency is None or ((now - self.interval.end) > self.update_frequency):
139
+ if (
140
+ force
141
+ or self.update_frequency is None
142
+ or ((now - self.interval.end) > self.update_frequency)
143
+ ):
136
144
  self._update(
137
145
  now=now,
138
146
  engine=engine,
@@ -143,7 +151,10 @@ class LiveData(object):
143
151
  def _get_aggregated_data(
144
152
  self,
145
153
  ) -> dict[str, DataFrame]:
146
- self.aggregated_data = {aggregation_name: aggregation(self.data) for aggregation_name, aggregation in self.aggregations.items()}
154
+ self.aggregated_data = {
155
+ aggregation_name: aggregation(self.data)
156
+ for aggregation_name, aggregation in self.aggregations.items()
157
+ }
147
158
 
148
159
  return self.aggregated_data
149
160
 
@@ -11,7 +11,10 @@ def get_queries_folders() -> tuple[Path, ...]:
11
11
  ROOT = get_root()
12
12
  return (
13
13
  ROOT / "queries",
14
- *[ROOT / "src" / module / "data" / "queries" for module in os.listdir(path=ROOT / "src")],
14
+ *[
15
+ ROOT / "src" / module / "data" / "queries"
16
+ for module in os.listdir(path=ROOT / "src")
17
+ ],
15
18
  Path(__file__).parent,
16
19
  )
17
20
 
@@ -44,7 +47,9 @@ def get_query(
44
47
  return read_file(path=path)
45
48
 
46
49
  except ValueError:
47
- raise ValueError(f"No such query {query_name} found in the query folders {', '.join(list(map(str, queries_folders)))} or at the path {path}")
50
+ raise ValueError(
51
+ f"No such query {query_name} found in the query folders {', '.join(list(map(str, queries_folders)))} or at the path {path}"
52
+ )
48
53
 
49
54
 
50
55
  def get_formatted_query(
@@ -10,8 +10,8 @@ from mayutils.data import CACHE_FOLDER
10
10
  from mayutils.data.queries import QUERIES_FOLDERS, get_formatted_query
11
11
  from mayutils.environment.filesystem import encode_path
12
12
  from mayutils.environment.memoisation import DataframeBackends
13
- from mayutils.objects.dataframes import DataFrames, read_parquet, to_parquet
14
13
  from mayutils.objects.hashing import hash_inputs
14
+ from mayutils.objects.dataframes import read_parquet, to_parquet, DataFrames
15
15
 
16
16
  if TYPE_CHECKING:
17
17
  from functools import _lru_cache_wrapper as LRUCacheWrapper
@@ -58,7 +58,11 @@ def get_query_data(
58
58
  cache: bool | Literal["persistent"] = True,
59
59
  **format_kwargs,
60
60
  ) -> DataFrames:
61
- if cache is False and hasattr(read_query, "cache_clear") and callable(getattr(read_query, "cache_clear")):
61
+ if (
62
+ cache is False
63
+ and hasattr(read_query, "cache_clear")
64
+ and callable(getattr(read_query, "cache_clear"))
65
+ ):
62
66
  read_query.cache_clear()
63
67
 
64
68
  cache_name = f"{encode_path(path=query_name)}_data_{
@@ -146,7 +146,9 @@ class Logger(logging.Logger):
146
146
  level: Optional[Level] = None,
147
147
  **kwargs,
148
148
  ) -> str:
149
- level_int = logging._nameToLevel.get(level, None) if isinstance(level, str) else level
149
+ level_int = (
150
+ logging._nameToLevel.get(level, None) if isinstance(level, str) else level
151
+ )
150
152
  if level_int is None:
151
153
  level_int = self.getEffectiveLevel()
152
154
 
@@ -107,7 +107,10 @@ class cache(object):
107
107
  **kwargs,
108
108
  )
109
109
 
110
- if self.maxsize is not None and len(self.persistent_cache) >= self.maxsize:
110
+ if (
111
+ self.maxsize is not None
112
+ and len(self.persistent_cache) >= self.maxsize
113
+ ):
111
114
  self.persistent_cache.popitem(last=False)
112
115
 
113
116
  self.misses += 1
@@ -64,9 +64,15 @@ def export_slides(
64
64
  format="%Y_%m_%d",
65
65
  )
66
66
 
67
- filepath = os.path.dirname(p=os.path.realpath(filename="__file__")) + "/" + file_name
67
+ filepath = (
68
+ os.path.dirname(p=os.path.realpath(filename="__file__")) + "/" + file_name
69
+ )
68
70
 
69
- file_title = f"{title}_{today}" if title is not None else f"{file_name.split(sep='.')[0]}_{today}"
71
+ file_title = (
72
+ f"{title}_{today}"
73
+ if title is not None
74
+ else f"{file_name.split(sep='.')[0]}_{today}"
75
+ )
70
76
  output_filepath = SLIDES_FOLDER / file_title
71
77
 
72
78
  with Progress(
@@ -1,5 +1,5 @@
1
1
  from pathlib import Path
2
- from typing import Callable, Literal, Optional, Self, TypeAlias, get_args
2
+ from typing import Callable, Literal, Optional, Self, get_args
3
3
 
4
4
  from itables import show
5
5
  from great_tables import GT
@@ -31,7 +31,7 @@ from mayutils.objects.colours import Colour
31
31
  from mayutils.export import OUTPUT_FOLDER
32
32
 
33
33
  DataframeBackends = Literal["pandas", "polars"]
34
- DataFrames: TypeAlias = pd.DataFrame | pl.DataFrame
34
+ DataFrames = pd.DataFrame | pl.DataFrame
35
35
 
36
36
  DATA_FOLDER = OUTPUT_FOLDER / "Data"
37
37
 
@@ -59,7 +59,12 @@ class Styler(Style):
59
59
  self,
60
60
  ) -> Self:
61
61
  def style_map(value):
62
- return "color: rgba(0,0,0,0); background-color: rgba(0, 0, 0, 0);" if isinstance(value, (float, int, np.floating, np.integer)) and np.isnan(value) else ""
62
+ return (
63
+ "color: rgba(0,0,0,0); background-color: rgba(0, 0, 0, 0);"
64
+ if isinstance(value, (float, int, np.floating, np.integer))
65
+ and np.isnan(value)
66
+ else ""
67
+ )
63
68
 
64
69
  return self.map(style_map=style_map)
65
70
 
@@ -97,7 +102,9 @@ class Styler(Style):
97
102
 
98
103
  for col_num in range(len(self.columns)):
99
104
  self._display_funcs[(row_num, col_num)] = ( # type: ignore
100
- row_formatter if not isinstance(row_formatter, str) else lambda x: format(x, row_formatter) # type: ignore
105
+ row_formatter
106
+ if not isinstance(row_formatter, str)
107
+ else lambda x: format(x, row_formatter) # type: ignore
101
108
  )
102
109
 
103
110
  return self
@@ -324,9 +331,13 @@ class DataframeUtilsAccessor(object):
324
331
  ) -> DataFrame:
325
332
  df_cut = self.df.loc[self.df.index < cutoff].copy()
326
333
  if aggregation is not None:
327
- df_cut.loc[f"{cutoff}+", :] = aggregation(self.df.loc[self.df.index >= cutoff])
334
+ df_cut.loc[f"{cutoff}+", :] = aggregation(
335
+ self.df.loc[self.df.index >= cutoff]
336
+ )
328
337
  df_cut.index = df_cut.index.astype(dtype=str)
329
- df_cut = df_cut.sort_index(key=lambda x: x.str.split(pat="+").str[0].astype(dtype=int))
338
+ df_cut = df_cut.sort_index(
339
+ key=lambda x: x.str.split(pat="+").str[0].astype(dtype=int)
340
+ )
330
341
 
331
342
  return df_cut
332
343
 
@@ -413,7 +424,9 @@ class SeriesUtilsAccessor(object):
413
424
  path: Path | str,
414
425
  ) -> Path:
415
426
  # TODO: Finish
416
- raise NotImplementedError("Not implemented for series yet: leverage existing df methods")
427
+ raise NotImplementedError(
428
+ "Not implemented for series yet: leverage existing df methods"
429
+ )
417
430
 
418
431
 
419
432
  class IndexUtilsAccessor(object):
@@ -430,7 +443,14 @@ class IndexUtilsAccessor(object):
430
443
  if not isinstance(self.index, MultiIndex):
431
444
  raise TypeError("Index is not of type MultiIndex")
432
445
 
433
- return list(map(list, self.index)) if not transpose else list(list(self.index.get_level_values(level=level)) for level in range(len(self.index.names)))
446
+ return (
447
+ list(map(list, self.index))
448
+ if not transpose
449
+ else list(
450
+ list(self.index.get_level_values(level=level))
451
+ for level in range(len(self.index.names))
452
+ )
453
+ )
434
454
 
435
455
 
436
456
  # def save_dataframe(
@@ -510,7 +530,7 @@ class IndexUtilsAccessor(object):
510
530
 
511
531
 
512
532
  def to_parquet(
513
- df: DataFrames,
533
+ df: DataFrames, # type: ignore
514
534
  path: Path | str,
515
535
  dataframe_backend: Optional[DataframeBackends] = None,
516
536
  **kwargs,
@@ -552,7 +572,7 @@ def read_parquet(
552
572
  path: Path | str,
553
573
  dataframe_backend: DataframeBackends = "pandas",
554
574
  **kwargs,
555
- ) -> DataFrames:
575
+ ) -> DataFrames: # type: ignore
556
576
  path = Path(path)
557
577
 
558
578
  if dataframe_backend == "pandas":
@@ -227,7 +227,9 @@ class Time(BaseTime):
227
227
  def fractional_completion(
228
228
  self,
229
229
  ) -> float:
230
- return (self.hour * 3600 + self.minute * 60 + self.second + self.microsecond * 1e-6) / DAY_SECONDS
230
+ return (
231
+ self.hour * 3600 + self.minute * 60 + self.second + self.microsecond * 1e-6
232
+ ) / DAY_SECONDS
231
233
 
232
234
 
233
235
  class DateTime(BaseDateTime):
@@ -580,7 +582,9 @@ class Intervals(object):
580
582
  def sort(
581
583
  self,
582
584
  ) -> Self:
583
- self.intervals = tuple(sorted(self.intervals, key=lambda interval: (interval.start, interval.end)))
585
+ self.intervals = tuple(
586
+ sorted(self.intervals, key=lambda interval: (interval.start, interval.end))
587
+ )
584
588
 
585
589
  return self
586
590
 
@@ -0,0 +1,61 @@
1
+ from typing import Optional
2
+ from re import sub
3
+
4
+
5
+ def noneish_string(
6
+ string: Optional[str],
7
+ ) -> Optional[str]:
8
+ return None if string == "" else string
9
+
10
+
11
+ def snakify(
12
+ string: str,
13
+ ) -> str:
14
+ return "_".join(
15
+ sub(
16
+ pattern="([A-Z][a-z]+)",
17
+ repl=r" \1",
18
+ string=sub(
19
+ pattern="([A-Z]+)",
20
+ repl=r" \1",
21
+ string=string.replace("-", " "),
22
+ ),
23
+ ).split()
24
+ ).lower()
25
+
26
+
27
+ def unsnakify(
28
+ string: str,
29
+ ) -> str:
30
+ return string.replace("_", " ").title()
31
+
32
+
33
+ def kebabify(
34
+ string: str,
35
+ ) -> str:
36
+ return "-".join(
37
+ sub(
38
+ pattern=r"(\s|_|-)+",
39
+ repl=" ",
40
+ string=sub(
41
+ pattern=r"[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+",
42
+ repl=r" \1",
43
+ string=string,
44
+ ).lower(),
45
+ ).split()
46
+ )
47
+
48
+
49
+ def camel(
50
+ string: str,
51
+ ) -> str:
52
+ string = (
53
+ sub(
54
+ pattern=r"(_|-)+",
55
+ repl=" ",
56
+ string=string,
57
+ )
58
+ .title()
59
+ .replace(" ", "")
60
+ )
61
+ return "".join([string[0].lower(), string[1:]])
@@ -5,7 +5,7 @@ from typing import Any, Literal, Optional, Self, final
5
5
  from dataclasses import dataclass, field
6
6
  from mayutils.export.images import IMAGES_FOLDER
7
7
  import numpy as np
8
- from scipy.stats import gaussian_kde, norm # pyright: ignore[reportAttributeAccessIssue]
8
+ from scipy.stats import gaussian_kde, norm
9
9
 
10
10
  import plotly.graph_objects as go
11
11
  import plotly.io as pio
@@ -21,13 +21,13 @@ from mayutils.visualisation.graphs.plotly.templates import (
21
21
  axis_dict,
22
22
  shuffled_colourscale,
23
23
  )
24
+ from plotly.basedatatypes import BaseTraceType as Trace
24
25
  from mayutils.visualisation.graphs.plotly.traces import (
25
26
  Null,
26
27
  Line,
27
28
  Scatter,
28
29
  Ecdf,
29
30
  Bar3d,
30
- Trace,
31
31
  is_trace_3d,
32
32
  )
33
33
 
@@ -586,7 +586,7 @@ class Plot(go.Figure):
586
586
  *args,
587
587
  **kwargs,
588
588
  ) -> Self:
589
- self.data: list[Trace] = []
589
+ self.data: Any = [] # pyright: ignore[reportIncompatibleMethodOverride]
590
590
 
591
591
  return self
592
592
 
@@ -10,7 +10,7 @@ import plotly.graph_objects as go
10
10
  from numpy.typing import ArrayLike
11
11
  from pandas import DataFrame, Series
12
12
  from plotly.basedatatypes import BaseTraceType as Trace
13
- from scipy.stats import gaussian_kde # pyright: ignore[reportAttributeAccessIssue]
13
+ from scipy.stats import gaussian_kde
14
14
 
15
15
  from mayutils.objects.colours import Colour
16
16
  from mayutils.objects.datetime import DateTime
@@ -41,19 +41,24 @@ class Line(go.Scatter):
41
41
  self,
42
42
  label_name: bool | str = False,
43
43
  textposition: str = "middle right",
44
+ meta: str = "line",
44
45
  *args,
45
46
  **kwargs,
46
47
  ) -> None:
47
48
  mode: str = kwargs.pop("mode", "lines")
48
- mode += "+text" if label_name is not False and not mode.endswith("+text") else ""
49
+ mode += (
50
+ "+text" if label_name is not False and not mode.endswith("+text") else ""
51
+ )
49
52
  kwargs["mode"] = mode
50
53
  kwargs["textposition"] = textposition
51
54
 
52
- label_name = (kwargs.get("name", None) if label_name is True else label_name) or ""
55
+ label_name = (
56
+ kwargs.get("name", None) if label_name is True else label_name
57
+ ) or ""
53
58
  kwargs["text"] = [""] * (len(kwargs.get("x", [])) - 1) + [label_name]
54
59
 
55
60
  super().__init__(
56
- meta="line",
61
+ meta=meta,
57
62
  *args,
58
63
  **kwargs,
59
64
  )
@@ -93,7 +98,9 @@ class Line(go.Scatter):
93
98
  for lower, upper in zip(y_lower, y_upper):
94
99
  if len(lower) != len(y) or len(upper) != len(y): # type: ignore
95
100
  raise ValueError("Y Values of different length provided")
96
- elif np.any(np.asarray(lower) > last_lower) or np.any(np.asarray(upper) < last_upper):
101
+ elif np.any(np.asarray(lower) > last_lower) or np.any(
102
+ np.asarray(upper) < last_upper
103
+ ):
97
104
  raise ValueError("Monotonic bounds not passed")
98
105
 
99
106
  last_lower = lower
@@ -124,7 +131,11 @@ class Line(go.Scatter):
124
131
  legendgroup=legendgroup,
125
132
  hoverinfo="skip",
126
133
  *[*args],
127
- **{key: value for key, value in kwargs.items() if key != "line_color"},
134
+ **{
135
+ key: value
136
+ for key, value in kwargs.items()
137
+ if key != "line_color"
138
+ },
128
139
  )
129
140
  for lower, upper in zip(y_lower, y_upper)
130
141
  ],
@@ -184,8 +195,11 @@ class Ecdf(Line):
184
195
 
185
196
  _y += y_shift
186
197
 
187
- kwargs["line_shape"] = "hv" if ((mode != "reversed") ^ (not left_inclusive)) else "vh"
198
+ kwargs["line_shape"] = (
199
+ "hv" if ((mode != "reversed") ^ (not left_inclusive)) else "vh"
200
+ )
188
201
  kwargs["fill"] = fill
202
+ kwargs["meta"] = kwargs.pop("meta", "ecdf")
189
203
 
190
204
  if fill == "toself":
191
205
  _x = np.insert(_x, 0, _x[-1])
@@ -202,8 +216,6 @@ class Ecdf(Line):
202
216
  **kwargs,
203
217
  )
204
218
 
205
- self.meta = "ecdf"
206
-
207
219
 
208
220
  class Kde(Line):
209
221
  def __init__(
@@ -219,6 +231,8 @@ class Kde(Line):
219
231
  _x_grid = np.linspace(np.min(_x), np.max(_x), 1000)
220
232
  _y = kde(_x_grid)
221
233
 
234
+ kwargs["meta"] = kwargs.pop("meta", "kde")
235
+
222
236
  super().__init__(
223
237
  x=_x_grid,
224
238
  y=_y,
@@ -228,8 +242,6 @@ class Kde(Line):
228
242
  **kwargs,
229
243
  )
230
244
 
231
- self.meta = "kde"
232
-
233
245
 
234
246
  class Scatter(go.Scatter):
235
247
  def __init__(
@@ -372,7 +384,11 @@ class Bar3d(go.Mesh3d):
372
384
  x_arr = np.asarray(x)
373
385
  y_arr = np.asarray(y)
374
386
  z_arr = np.asarray(z, dtype=np.float64)
375
- w_arr = np.asarray(w, dtype=np.float64) if w is not None else np.ones(z_arr.shape, dtype=np.float64)
387
+ w_arr = (
388
+ np.asarray(w, dtype=np.float64)
389
+ if w is not None
390
+ else np.ones(z_arr.shape, dtype=np.float64)
391
+ )
376
392
 
377
393
  if any(len(arr) != len(w_arr) for arr in [x_arr, y_arr, z_arr]):
378
394
  raise ValueError("Input arrays are not same length")
@@ -390,7 +406,12 @@ class Bar3d(go.Mesh3d):
390
406
  )
391
407
  * dx
392
408
  )
393
- self._x = np.stack([x_arr_numerical - dx / 2, x_arr_numerical + dx / 2], axis=1)[np.arange(x_arr_numerical.size)[:, None], [0, 0, 1, 1, 0, 0, 1, 1]].reshape(-1) + x_start
409
+ self._x = (
410
+ np.stack([x_arr_numerical - dx / 2, x_arr_numerical + dx / 2], axis=1)[
411
+ np.arange(x_arr_numerical.size)[:, None], [0, 0, 1, 1, 0, 0, 1, 1]
412
+ ].reshape(-1)
413
+ + x_start
414
+ )
394
415
  y_arr_numerical = (
395
416
  map_categorical_array(
396
417
  arr=self._y_arr,
@@ -398,18 +419,34 @@ class Bar3d(go.Mesh3d):
398
419
  )
399
420
  * dy
400
421
  )
401
- self._y = np.stack([y_arr_numerical - dy / 2, y_arr_numerical + dy / 2], axis=1)[np.arange(y_arr_numerical.size)[:, None], [0, 1, 1, 0, 0, 1, 1, 0]].reshape(-1) + y_start
422
+ self._y = (
423
+ np.stack([y_arr_numerical - dy / 2, y_arr_numerical + dy / 2], axis=1)[
424
+ np.arange(y_arr_numerical.size)[:, None], [0, 1, 1, 0, 0, 1, 1, 0]
425
+ ].reshape(-1)
426
+ + y_start
427
+ )
402
428
  self._z = np.ones(self._z_arr.size * 8, dtype=self._z_arr.dtype) * z0
403
- self._z[(np.arange(self._z_arr.size) * 8)[:, None] + np.array([4, 5, 6, 7])] = self._z_arr[:, None]
429
+ self._z[(np.arange(self._z_arr.size) * 8)[:, None] + np.array([4, 5, 6, 7])] = (
430
+ self._z_arr[:, None]
431
+ )
404
432
  self._z += z_start
405
433
  self._w = np.repeat(
406
434
  self._w_arr,
407
435
  repeats=8,
408
436
  )
409
437
 
410
- i = (np.tile([7, 0, 0, 0, 4, 4, 6, 6, 4, 0, 3, 2], (len(self._x_arr), 1)) + np.arange(len(self._x_arr))[:, np.newaxis] * 8).flatten()
411
- j = (np.tile([3, 4, 1, 2, 5, 6, 5, 2, 0, 1, 6, 3], (len(self._x_arr), 1)) + np.arange(len(self._x_arr))[:, np.newaxis] * 8).flatten()
412
- k = (np.tile([0, 7, 2, 3, 6, 7, 1, 1, 5, 5, 7, 6], (len(self._x_arr), 1)) + np.arange(len(self._x_arr))[:, np.newaxis] * 8).flatten()
438
+ i = (
439
+ np.tile([7, 0, 0, 0, 4, 4, 6, 6, 4, 0, 3, 2], (len(self._x_arr), 1))
440
+ + np.arange(len(self._x_arr))[:, np.newaxis] * 8
441
+ ).flatten()
442
+ j = (
443
+ np.tile([3, 4, 1, 2, 5, 6, 5, 2, 0, 1, 6, 3], (len(self._x_arr), 1))
444
+ + np.arange(len(self._x_arr))[:, np.newaxis] * 8
445
+ ).flatten()
446
+ k = (
447
+ np.tile([0, 7, 2, 3, 6, 7, 1, 1, 5, 5, 7, 6], (len(self._x_arr), 1))
448
+ + np.arange(len(self._x_arr))[:, np.newaxis] * 8
449
+ ).flatten()
413
450
 
414
451
  return super().__init__(
415
452
  x=self._x,
@@ -474,9 +511,18 @@ def merge_cuboids(
474
511
  y = np.zeros(len(cuboids) * 8)
475
512
  z = np.zeros(len(cuboids) * 8)
476
513
  intensity = np.zeros(len(cuboids) * 8)
477
- i = (np.tile([7, 0, 0, 0, 4, 4, 6, 6, 4, 0, 3, 2], (len(cuboids), 1)) + np.arange(len(cuboids))[:, np.newaxis] * 8).flatten()
478
- j = (np.tile([3, 4, 1, 2, 5, 6, 5, 2, 0, 1, 6, 3], (len(cuboids), 1)) + np.arange(len(cuboids))[:, np.newaxis] * 8).flatten()
479
- k = (np.tile([0, 7, 2, 3, 6, 7, 1, 1, 5, 5, 7, 6], (len(cuboids), 1)) + np.arange(len(cuboids))[:, np.newaxis] * 8).flatten()
514
+ i = (
515
+ np.tile([7, 0, 0, 0, 4, 4, 6, 6, 4, 0, 3, 2], (len(cuboids), 1))
516
+ + np.arange(len(cuboids))[:, np.newaxis] * 8
517
+ ).flatten()
518
+ j = (
519
+ np.tile([3, 4, 1, 2, 5, 6, 5, 2, 0, 1, 6, 3], (len(cuboids), 1))
520
+ + np.arange(len(cuboids))[:, np.newaxis] * 8
521
+ ).flatten()
522
+ k = (
523
+ np.tile([0, 7, 2, 3, 6, 7, 1, 1, 5, 5, 7, 6], (len(cuboids), 1))
524
+ + np.arange(len(cuboids))[:, np.newaxis] * 8
525
+ ).flatten()
480
526
 
481
527
  for idx, cuboid in enumerate(cuboids):
482
528
  x[idx * 8 : (idx + 1) * 8] = cuboid.x # type: ignore
@@ -1,7 +0,0 @@
1
- from typing import Optional
2
-
3
-
4
- def noneish_string(
5
- string: Optional[str],
6
- ) -> Optional[str]:
7
- return None if string == "" else string
File without changes
File without changes