mipi-datamanager 1.4.7__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 (32) hide show
  1. mipi_datamanager-1.4.7/PKG-INFO +35 -0
  2. mipi_datamanager-1.4.7/README.md +3 -0
  3. mipi_datamanager-1.4.7/pyproject.toml +40 -0
  4. mipi_datamanager-1.4.7/src/mipi_datamanager/__init__.py +14 -0
  5. mipi_datamanager-1.4.7/src/mipi_datamanager/connection.py +54 -0
  6. mipi_datamanager-1.4.7/src/mipi_datamanager/core/__init__.py +0 -0
  7. mipi_datamanager-1.4.7/src/mipi_datamanager/core/common.py +183 -0
  8. mipi_datamanager-1.4.7/src/mipi_datamanager/core/data_managers.py +1622 -0
  9. mipi_datamanager-1.4.7/src/mipi_datamanager/core/docs/__init__.py +0 -0
  10. mipi_datamanager-1.4.7/src/mipi_datamanager/core/docs/contribute.py +5 -0
  11. mipi_datamanager-1.4.7/src/mipi_datamanager/core/docs/getting_started.py +261 -0
  12. mipi_datamanager-1.4.7/src/mipi_datamanager/core/docs/install.py +17 -0
  13. mipi_datamanager-1.4.7/src/mipi_datamanager/core/docs/patch_notes.py +3 -0
  14. mipi_datamanager-1.4.7/src/mipi_datamanager/core/docs/setup.py +54 -0
  15. mipi_datamanager-1.4.7/src/mipi_datamanager/core/file_search.md +64 -0
  16. mipi_datamanager-1.4.7/src/mipi_datamanager/core/file_search.py +429 -0
  17. mipi_datamanager-1.4.7/src/mipi_datamanager/core/file_search.tcss +8 -0
  18. mipi_datamanager-1.4.7/src/mipi_datamanager/core/file_search_gui.py +633 -0
  19. mipi_datamanager-1.4.7/src/mipi_datamanager/core/jinja/__init__.py +2 -0
  20. mipi_datamanager-1.4.7/src/mipi_datamanager/core/jinja/filters.py +55 -0
  21. mipi_datamanager-1.4.7/src/mipi_datamanager/core/jinja/jinja.py +329 -0
  22. mipi_datamanager-1.4.7/src/mipi_datamanager/core/meta.py +68 -0
  23. mipi_datamanager-1.4.7/src/mipi_datamanager/core/read_setup.py +313 -0
  24. mipi_datamanager-1.4.7/src/mipi_datamanager/core/templates/jinja_header.txt +11 -0
  25. mipi_datamanager-1.4.7/src/mipi_datamanager/core/templates/jinja_repo_header.txt +14 -0
  26. mipi_datamanager-1.4.7/src/mipi_datamanager/core/templates/mipi_summary.txt +0 -0
  27. mipi_datamanager-1.4.7/src/mipi_datamanager/errors.py +38 -0
  28. mipi_datamanager-1.4.7/src/mipi_datamanager/formatters.py +264 -0
  29. mipi_datamanager-1.4.7/src/mipi_datamanager/generate_inserts.py +148 -0
  30. mipi_datamanager-1.4.7/src/mipi_datamanager/query.py +127 -0
  31. mipi_datamanager-1.4.7/src/mipi_datamanager/types.py +6 -0
  32. mipi_datamanager-1.4.7/src/mipi_datamanager/wrangle.py +133 -0
@@ -0,0 +1,35 @@
1
+ Metadata-Version: 2.4
2
+ Name: mipi_datamanager
3
+ Version: 1.4.7
4
+ Summary:
5
+ Author: murphy-sean1
6
+ Author-email: murphy-sean1@cooperhealth.edu
7
+ Requires-Python: >=3.12,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Classifier: Programming Language :: Python :: 3.14
12
+ Requires-Dist: flask (>=3.0.3,<4.0.0)
13
+ Requires-Dist: flask-bcrypt (>=1.0.1,<2.0.0)
14
+ Requires-Dist: flask-restful (>=0.3.10,<0.4.0)
15
+ Requires-Dist: jinja2 (>=3.1.2,<4.0.0)
16
+ Requires-Dist: jinjasql2 (>=0.1.12,<0.2.0)
17
+ Requires-Dist: mkdocs (>=1.6.0,<2.0.0)
18
+ Requires-Dist: mkdocs-material (>=9.5.33,<10.0.0)
19
+ Requires-Dist: mkdocstrings[python] (>=0.26.1,<0.27.0)
20
+ Requires-Dist: openpyxl (>=3.1.5,<4.0.0)
21
+ Requires-Dist: pandas (>=2.2.2,<3.0.0)
22
+ Requires-Dist: pylint (>=3.3.3,<4.0.0)
23
+ Requires-Dist: pymdown-extensions (>=10.11.2,<11.0.0)
24
+ Requires-Dist: pyodbc (>=5.1.0,<6.0.0)
25
+ Requires-Dist: pytest (>=8.3.2,<9.0.0)
26
+ Requires-Dist: pytest-coverage (>=0.0,<0.1)
27
+ Requires-Dist: sqlalchemy (>=2.0.35,<3.0.0)
28
+ Requires-Dist: textual-dev (>=1.7.0,<2.0.0)
29
+ Requires-Dist: textual[syntax] (>=3.2.0,<4.0.0)
30
+ Description-Content-Type: text/markdown
31
+
32
+ # mipi_datamanager
33
+
34
+ Python utilities and pipelines for the MiPi Data Manager. See docs and usage in the repo.
35
+
@@ -0,0 +1,3 @@
1
+ # mipi_datamanager
2
+
3
+ Python utilities and pipelines for the MiPi Data Manager. See docs and usage in the repo.
@@ -0,0 +1,40 @@
1
+ [tool.poetry]
2
+ name = "mipi_datamanager"
3
+ version = "1.4.7"
4
+ description = ""
5
+ authors = ["murphy-sean1 <murphy-sean1@cooperhealth.edu>"]
6
+ readme = "README.md"
7
+
8
+ [tool.poetry.dependencies]
9
+ python = "^3.12"
10
+ pandas = "^2.2.2"
11
+ jinja2 = "^3.1.2"
12
+ pytest = "^8.3.2"
13
+ pyodbc = "^5.1.0"
14
+ openpyxl = "^3.1.5"
15
+ pytest-coverage = "^0.0"
16
+ mkdocs = "^1.6.0"
17
+ mkdocs-material = "^9.5.33"
18
+ flask = "^3.0.3"
19
+ flask-restful = "^0.3.10"
20
+ flask-bcrypt = "^1.0.1"
21
+ sqlalchemy = "^2.0.35"
22
+ mkdocstrings = {extras = ["python"], version = "^0.26.1"}
23
+ pymdown-extensions = "^10.11.2"
24
+ pylint = "^3.3.3"
25
+ textual = {extras = ["syntax"], version = "^3.2.0"}
26
+ textual-dev = "^1.7.0"
27
+ jinjasql2 = "^0.1.12"
28
+
29
+
30
+ [tool.poetry.group.dev.dependencies]
31
+ pandas-stubs = "^2.2.3.250308"
32
+ bump-my-version = "^1.2.1"
33
+ pytest-mock = "^3.15.0"
34
+
35
+ [build-system]
36
+ requires = ["poetry-core"]
37
+ build-backend = "poetry.core.masonry.api"
38
+
39
+ [tool.poetry.scripts]
40
+ mipi-app = "mipi_datamanager.core.file_search_gui:main"
@@ -0,0 +1,14 @@
1
+ from mipi_datamanager.core.data_managers import DataManager
2
+ from mipi_datamanager.core.jinja import JinjaLibrary, JinjaRepo, exc
3
+ from mipi_datamanager.core.file_search import FileSearch
4
+
5
+ __all__ = ['DataManager', 'JinjaLibrary', 'JinjaRepo', 'FileSearch', "exc"]
6
+
7
+ # import doc pages for mkdocstrings
8
+ from mipi_datamanager.core.docs import (
9
+ setup,
10
+ getting_started,
11
+ contribute,
12
+ patch_notes,
13
+ install
14
+ )
@@ -0,0 +1,54 @@
1
+ import datetime as dt
2
+ from sqlalchemy import create_engine
3
+
4
+ class Odbc:
5
+ """
6
+ Creates an Odbc connection object that can be used in any MiPi function that queries a database. This function
7
+ automatically handles setup and taredown of the connection even if there is an error.
8
+
9
+ Args:
10
+ dsn: The DSN name used to configure the connection
11
+ """
12
+
13
+ def __init__(self, dsn:str = None, driver = None, server = None, database = None, uid = None, pwd = None, trusted_connection = "yes", dialect = "mssql"):
14
+
15
+ self.dsn = dsn
16
+ self.driver = driver
17
+ self.server = server
18
+ self.database = database
19
+ self.uid = uid
20
+ self.pwd = pwd
21
+ self.trusted_connection = trusted_connection
22
+ self.dialect = dialect
23
+
24
+ self.name = self.dsn or self.database
25
+
26
+
27
+ @property
28
+ def connection_string(self):
29
+ params = {self.dsn:"?odbc_connect=DSN",
30
+ self.driver:"DRIVER",
31
+ self.server:"SERVER",
32
+ self.database:"DATABASE",
33
+ self.uid:"UID",
34
+ self.pwd:"PWD",
35
+ self.trusted_connection:"Trusted_Connection"}
36
+ con_str = f"mssql+pyodbc:///"
37
+ for param,key in params.items():
38
+ if param:
39
+ con_str += f"{key}={param};"
40
+
41
+ return con_str
42
+
43
+ def __enter__(self):
44
+ self.engine = create_engine(self.connection_string)
45
+ self.con = self.engine.connect()
46
+ self.start = dt.datetime.now()
47
+ print(f"\nConnection Established: {self.name} @ {self.start}")
48
+ return self.con
49
+
50
+ def __exit__(self, exc_type, exc_val, exc_tb):
51
+ self.con.close()
52
+ end = dt.datetime.now()
53
+ print(f"Connection Terminated: {self.name} @ {end}")
54
+ print(f"Connection Open For: {end - self.start}")
@@ -0,0 +1,183 @@
1
+ from typing import Any, Sequence
2
+ from pathlib import Path
3
+
4
+ def read_text_file(file: str | Path, encoding ="utf-8") -> str:
5
+ with open(file, "r",encoding = encoding) as f:
6
+ contents = f.read()
7
+ return contents
8
+
9
+
10
+ def _maybe_add_lists(list_of_lists):
11
+ if not isinstance(list_of_lists, list):
12
+ _list_of_lists = [list_of_lists]
13
+
14
+ final_list = []
15
+ for list_i in list_of_lists:
16
+ final_list += list_i
17
+
18
+ return final_list
19
+
20
+
21
+ def _maybe_convert_tuple_to_list(val: list | tuple):
22
+ if isinstance(val, tuple):
23
+ return list(val)
24
+ elif isinstance(val, list):
25
+ return val
26
+ else:
27
+ raise TypeError(f'Expected tuple or list, got {type(val)}')
28
+
29
+
30
+ def ensure_list(val: Any) -> list:
31
+ """
32
+ Wrap a single value in a list, or convert a list-like object into a list if it isnt one
33
+ """
34
+ if isinstance(val, list):
35
+ return val
36
+ if isinstance(val, Sequence) and not isinstance(val, (str, bytes)):
37
+ return list(val)
38
+ return [val]
39
+
40
+
41
+ def dict_to_string(dictionary: dict):
42
+ """
43
+
44
+ creates a clean string representation of a dictionary
45
+
46
+ Args:
47
+ dictionary: any dictionary
48
+ block_pad: int: number of lines to pad top and bottom of output
49
+
50
+ Returns: Clean string representation of the dictionary
51
+
52
+ """
53
+
54
+ max_key_length = max(len(str(key)) for key in dictionary.keys())
55
+ max_value_length = max(len(str(value)) for value in dictionary.values())
56
+
57
+ output = ""
58
+ for i, (key, value) in enumerate(dictionary.items()):
59
+ if i != 0:
60
+ output += ","
61
+ output += f"{str(key):<{max_key_length}} : {str(value):>{max_value_length}}\n"
62
+
63
+ return output
64
+
65
+ def _maybe_rename_values(value: str|list, rename:dict):
66
+ if rename is None:
67
+ _rename = {}
68
+ else:
69
+ _rename = rename
70
+
71
+ if isinstance(value, str):
72
+ if value in _rename:
73
+ return rename[value]
74
+ else:
75
+ return value
76
+ elif isinstance(value, list):
77
+ return [_rename[i] if i in _rename else i for i in value]
78
+ else:
79
+ raise TypeError(f"expected type string or list got {type(value)}")
80
+
81
+ def _columns_to_dict(df):
82
+ return {f"'{col}'": None for col in df.columns.tolist()}
83
+
84
+ def _dict_to_string(dictionary: dict):
85
+ """
86
+
87
+ creates a clean string representation of a dictionary
88
+
89
+ Args:
90
+ dictionary: any dictionary
91
+ block_pad: int: number of lines to pad top and bottom of output
92
+
93
+ Returns: Clean string representation of the dictionary
94
+
95
+ """
96
+ if not isinstance(dictionary, dict):
97
+ raise TypeError(f"expected dict got {type(dictionary)}")
98
+
99
+ if len(dictionary) == 0:
100
+ raise ValueError("Dictionary is empty")
101
+
102
+ max_key_length = max(len(str(key)) for key in dictionary.keys())
103
+ max_value_length = max(len(str(value)) for value in dictionary.values())
104
+
105
+ output = ""
106
+ for i, (key, value) in enumerate(dictionary.items()):
107
+ if i != 0:
108
+ output += ","
109
+ output += f"{str(key):<{max_key_length}} : {str(value):>{max_value_length}}\n"
110
+
111
+ return output
112
+
113
+ class IndexedDict(dict):
114
+ """
115
+ A dictionary object that allows you to access items by their key index.
116
+ The key index is the suffix of the key.
117
+ """
118
+
119
+ def __init__(self, *args, **kwargs):
120
+ super().__init__(*args, **kwargs)
121
+ for key in self.keys():
122
+ self._validate_key(key)
123
+
124
+ def _validate_key(self, key):
125
+ if not isinstance(key, str):
126
+ raise TypeError('Key must be a string')
127
+
128
+ suffix = key.split("_")[-1]
129
+ prefix = key.split("_")[0]
130
+
131
+ if not suffix.isnumeric() or "_" not in key or len(prefix) == 0:
132
+ raise ValueError('Key must have a string name followed by an underscored numeric suffix "{keyname}_{index_number}"')
133
+
134
+ if suffix in [k.split("_")[-1] for k in self.keys() if k != key]:
135
+ raise ValueError('A key with that suffix already exists')
136
+
137
+
138
+ def __setitem__(self, key, value):
139
+ self._validate_key(key)
140
+ super().__setitem__(key, value)
141
+
142
+ def __getitem__(self, key):
143
+ if isinstance(key, str):
144
+ return super().__getitem__(key)
145
+ if isinstance(key, int):
146
+ ks = [k for k in self.keys() if k.endswith(str(key))]
147
+ assert len(ks) == 1, f"Assertion filed, multiple Keys exist for index{key}"
148
+ k = ks[0]
149
+ return super().__getitem__(k)
150
+ def update(self, other, **kwargs):
151
+ if isinstance(other, dict):
152
+ for key in other.keys():
153
+ self._validate_key(key)
154
+ for key in kwargs.keys():
155
+ self._validate_key(key)
156
+ super().update(other, **kwargs)
157
+ def get(self, key, default=None):
158
+ if isinstance(key, str):
159
+ return super().get(key, default)
160
+ if isinstance(key, int):
161
+ ks = [k for k in self.keys() if k.endswith(str(key))]
162
+ if len(ks) == 1:
163
+ return super().__getitem__(ks[0])
164
+ return default
165
+
166
+ class EnsureList:
167
+ def __set_name__(self, owner: type, name: str):
168
+ # called once, per attribute, at class creation time
169
+ self.private_name = f"_{name}"
170
+
171
+ def __get__(self, instance, owner: type):
172
+ if instance is None:
173
+ return self
174
+ # if not yet set, default to empty list
175
+ return getattr(instance, self.private_name, [])
176
+
177
+ def __set__(self, instance, value: Any):
178
+ if value is None:
179
+ set_val = []
180
+ else:
181
+ set_val = ensure_list(value)
182
+ setattr(instance, self.private_name, set_val)
183
+