velocity-python 0.0.31__py3-none-any.whl → 0.0.32__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.

Potentially problematic release.


This version of velocity-python might be problematic. Click here for more details.

@@ -3,46 +3,31 @@ import codecs
3
3
  import decimal
4
4
  from datetime import datetime, date, time
5
5
  from pprint import pformat
6
+ from typing import Optional, Union, List, Callable
6
7
 
8
+ # Convert SQL data to JS format for display
7
9
 
8
- def none(data):
9
- if data == None:
10
- return ""
11
- if data == "None":
12
- return ""
13
- if data == "null":
14
- return ""
15
- return data
16
10
 
11
+ def none(data: Optional[str]) -> str:
12
+ """Converts various 'null' representations to an empty string."""
13
+ return "" if data in (None, "None", "null", "@NULL") else data
17
14
 
18
- def phone(data):
19
- if data == None:
20
- return ""
21
- if data == "None":
15
+
16
+ def phone(data: Optional[str]) -> str:
17
+ """Formats a 10-digit phone number as (XXX) XXX-XXXX or returns an empty string if invalid."""
18
+ if data in (None, "None", ""):
22
19
  return ""
23
- if not data:
24
- return data
25
- data = re.search(r"\d{10}$", re.sub("[^0-9]", "", data)).group()
26
- return "({}) {}-{}".format(data[:3], data[3:6], data[6:])
20
+ digits = re.sub(r"[^0-9]", "", data)
21
+ match = re.search(r"\d{10}$", digits)
22
+ if match:
23
+ num = match.group()
24
+ return f"({num[:3]}) {num[3:6]}-{num[6:]}"
25
+ return ""
27
26
 
28
27
 
29
- def day_of_week(data, abbrev=False):
30
- if isinstance(data, list):
31
- new = []
32
- for day in data:
33
- new.append(day_of_week(day, abbrev=abbrev))
34
- return ",".join(new)
35
- if data == None:
36
- return ""
37
- if data == "None":
38
- return ""
39
- if not data:
40
- return data
41
- if abbrev:
42
- return {1: "Mon", 2: "Tue", 3: "Wed", 4: "Thu", 5: "Fri", 6: "Sat", 7: "Sun"}[
43
- int(data)
44
- ]
45
- return {
28
+ def day_of_week(data: Union[int, str, List], abbrev: bool = False) -> str:
29
+ """Converts a day number (1-7) to a day name, abbreviated if specified. Supports lists."""
30
+ days_full = {
46
31
  1: "Monday",
47
32
  2: "Tuesday",
48
33
  3: "Wednesday",
@@ -50,159 +35,136 @@ def day_of_week(data, abbrev=False):
50
35
  5: "Friday",
51
36
  6: "Saturday",
52
37
  7: "Sunday",
53
- }[int(data)]
54
-
55
-
56
- def date(*args, **kwds):
57
- kwds.setdefault("fmt", "%Y-%m-%d")
58
-
59
- def _(param):
60
- if isinstance(param, (datetime, date)):
61
- return param.strftime(kwds["fmt"])
62
- else:
63
- return param
64
-
65
- if args and args[0]:
66
- return _(args[0])
67
- return _
38
+ }
39
+ days_abbrev = {1: "Mon", 2: "Tue", 3: "Wed", 4: "Thu", 5: "Fri", 6: "Sat", 7: "Sun"}
40
+ days = days_abbrev if abbrev else days_full
68
41
 
42
+ if isinstance(data, list):
43
+ return ",".join(day_of_week(day, abbrev) for day in data if day in days)
69
44
 
70
- def time(*args, **kwds):
71
- kwds.setdefault("fmt", "%X")
45
+ try:
46
+ return days[int(data)]
47
+ except (ValueError, KeyError, TypeError):
48
+ return ""
72
49
 
73
- def _(param):
74
- if isinstance(param, (datetime, time)):
75
- return param.strftime(kwds["fmt"])
76
- else:
77
- return param
78
50
 
79
- if args and args[0]:
80
- return _(args[0])
81
- return _
51
+ def date(data: Union[datetime, date, str], fmt: str = "%Y-%m-%d") -> str:
52
+ """Formats a date object as a string according to the specified format."""
53
+ return data.strftime(fmt) if isinstance(data, (datetime, date)) else str(data)
82
54
 
83
55
 
84
- def timestamp(*args, **kwds):
85
- kwds.setdefault("fmt", "%c")
56
+ def time(data: Union[datetime, time, str], fmt: str = "%X") -> str:
57
+ """Formats a time object as a string according to the specified format."""
58
+ return data.strftime(fmt) if isinstance(data, (datetime, time)) else str(data)
86
59
 
87
- def _(param):
88
- if isinstance(param, (datetime)):
89
- return param.strftime(kwds["fmt"])
90
- else:
91
- return param
92
60
 
93
- if args:
94
- return _(args[0])
95
- return _
61
+ def timestamp(data: Union[datetime, str], fmt: str = "%c") -> str:
62
+ """Formats a datetime object as a string according to the specified format."""
63
+ return data.strftime(fmt) if isinstance(data, datetime) else str(data)
96
64
 
97
65
 
98
- def email(data):
99
- if data == None:
100
- return ""
101
- if data == "None":
102
- return ""
103
- return data.lower()
66
+ def email(data: Optional[str]) -> str:
67
+ """Returns a lowercase email address or an empty string if invalid."""
68
+ return "" if data in (None, "None") else data.lower()
104
69
 
105
70
 
106
- def pointer(data):
71
+ def pointer(data: Union[str, int]) -> Union[int, str]:
72
+ """Converts a string to an integer, or returns an empty string if conversion fails."""
107
73
  try:
108
74
  return int(data)
109
- except:
75
+ except (ValueError, TypeError):
110
76
  return ""
111
77
 
112
78
 
113
- def rot13(data):
79
+ def rot13(data: str) -> str:
80
+ """Encodes a string using ROT13."""
114
81
  return codecs.decode(data, "rot13")
115
82
 
116
83
 
117
- def boolean(data):
118
- if isinstance(data, str):
119
- if data.lower() in ["false", "", "f", "off", "no"]:
120
- return False
84
+ def boolean(data: Union[str, bool]) -> bool:
85
+ """Converts various representations to a boolean."""
86
+ if isinstance(data, str) and data.lower() in ["false", "", "f", "off", "no"]:
87
+ return False
121
88
  return bool(data)
122
89
 
123
90
 
124
- def money(data):
91
+ def money(data: str) -> str:
92
+ """Formats a numeric string as currency."""
125
93
  if data in [None, ""]:
126
94
  return ""
127
- data = re.sub("[^0-9\.-]", "", str(data))
128
- return "${:,.2f}".format(decimal.Decimal(data))
95
+ cleaned_data = re.sub(r"[^0-9\.-]", "", str(data))
96
+ return f"${decimal.Decimal(cleaned_data):,.2f}"
129
97
 
130
98
 
131
- def round(precision, data=None):
132
- def function(data):
133
- data = re.sub("[^0-9\.]", "", str(data))
134
- if data == "":
135
- return "0"
136
- return "{:.{prec}f}".format(decimal.Decimal(data), prec=precision)
99
+ def round_to(
100
+ precision: int, data: Optional[Union[str, float, decimal.Decimal]] = None
101
+ ) -> Union[Callable, str]:
102
+ """Rounds a number to the specified precision."""
137
103
 
138
- if data == None:
139
- return function
140
- return function(data)
104
+ def function(value):
105
+ cleaned_value = re.sub(r"[^0-9\.-]", "", str(value))
106
+ return (
107
+ f"{decimal.Decimal(cleaned_value):.{precision}f}" if cleaned_value else "0"
108
+ )
141
109
 
110
+ return function if data is None else function(data)
142
111
 
143
- def ein(data):
144
- if data == None:
145
- return ""
146
- if data == "None":
112
+
113
+ def ein(data: str) -> str:
114
+ """Formats a 9-digit EIN as XX-XXXXXXX or returns an empty string if invalid."""
115
+ if data in (None, "None", ""):
147
116
  return ""
148
- if not data:
149
- return data
150
- data = re.search(r"\d{9}$", re.sub("[^0-9]", "", data)).group()
151
- return "{}-{}".format(data[:2], data[2:])
117
+ cleaned_data = re.sub(r"[^0-9]", "", data)
118
+ match = re.fullmatch(r"\d{9}", cleaned_data)
119
+ return f"{cleaned_data[:2]}-{cleaned_data[2:]}" if match else ""
152
120
 
153
121
 
154
- def list(data):
122
+ def to_list(data: Union[str, List]) -> Optional[List]:
123
+ """Converts a single element or JSON-like list string to a list."""
155
124
  if data in (None, "None"):
156
125
  return None
157
126
  if isinstance(data, list):
158
127
  return data
159
- if isinstance(data, str):
160
- if data[0] == "[":
161
- return eval(data)
128
+ if isinstance(data, str) and data.startswith("["):
129
+ try:
130
+ return eval(data) # Be cautious with eval; only use if data is trusted
131
+ except (SyntaxError, NameError):
132
+ return None
162
133
  return [data]
163
134
 
164
135
 
165
- def title(data):
166
- if data == None:
167
- return ""
168
- if data == "None":
169
- return ""
170
- return str(data).title()
136
+ def title(data: Optional[str]) -> str:
137
+ """Converts a string to title case."""
138
+ return "" if data in (None, "None") else str(data).title()
171
139
 
172
140
 
173
- def lower(data):
174
- if data == None:
175
- return ""
176
- if data == "None":
177
- return ""
178
- return str(data).lower()
141
+ def lower(data: Optional[str]) -> str:
142
+ """Converts a string to lowercase."""
143
+ return "" if data in (None, "None") else str(data).lower()
179
144
 
180
145
 
181
- def upper(data):
182
- if data == None:
183
- return ""
184
- if data == "None":
185
- return ""
186
- return str(data).upper()
146
+ def upper(data: Optional[str]) -> str:
147
+ """Converts a string to uppercase."""
148
+ return "" if data in (None, "None") else str(data).upper()
149
+
187
150
 
151
+ def padding(length: int, char: str) -> Callable[[str], str]:
152
+ """Returns a function that pads a string to the specified length with the given character."""
188
153
 
189
- def padding(length, char):
190
- def inner(data):
191
- if data is None:
192
- return ""
193
- return str(data).rjust(length, char)
154
+ def inner(data: str) -> str:
155
+ return str(data).rjust(length, char) if data not in (None, "None", "") else ""
194
156
 
195
157
  return inner
196
158
 
197
159
 
198
- def pprint(data):
160
+ def pprint(data: str) -> str:
161
+ """Pretty-prints a JSON-like string representation of data."""
199
162
  try:
200
163
  return pformat(eval(data))
201
- except:
164
+ except (SyntaxError, NameError):
202
165
  return data
203
166
 
204
167
 
205
- def string(data):
206
- if data == None:
207
- return ""
208
- return str(data)
168
+ def string(data: Optional[str]) -> str:
169
+ """Converts a None value to an empty string; otherwise returns the string itself."""
170
+ return "" if data is None else str(data)
velocity/misc/export.py CHANGED
@@ -1,143 +1,146 @@
1
+ import decimal
2
+ import json
3
+ from datetime import datetime, date, time, timedelta
4
+ from typing import Union, List, Dict
5
+ from io import BytesIO
6
+ import base64
1
7
  import openpyxl
2
8
  from openpyxl.styles import NamedStyle, Font, Border, Side, Alignment
3
9
  from openpyxl.utils import get_column_letter
4
- from io import BytesIO
5
- import base64
6
10
 
7
11
 
8
- def extract(d, keys):
9
- return [d[key] for key in keys]
12
+ def extract(d: dict, keys: List[str]) -> List:
13
+ """Extract values from a dictionary based on a list of keys."""
14
+ return [d.get(key) for key in keys]
10
15
 
11
16
 
12
- def autosize_columns(ws, fixed={}):
13
- # Try to autosize the columns (doesn't always work due to dynamic
14
- # content, font family and font size differences, etc.) There is no
15
- # easy way to do this when buiding excel files.
17
+ def autosize_columns(ws, fixed: Dict[str, float] = {}):
18
+ """Autosize columns in the worksheet based on content length."""
16
19
  for col in ws.columns:
17
20
  max_length = 0
18
21
  for cell in col:
19
- try: # Necessary to avoid error on empty cells
20
- if len(str(cell.value)) > max_length:
21
- max_length = len(cell.value)
22
- except:
23
- pass
22
+ try:
23
+ if cell.value and len(str(cell.value)) > max_length:
24
+ max_length = len(str(cell.value))
25
+ except Exception:
26
+ continue
24
27
  adjusted_width = (max_length + 2) * 1.2
25
- try:
26
- l = get_column_letter(col[0].column)
27
- if l in fixed:
28
- adjusted_width = fixed["l"]
29
- ws.column_dimensions[l].width = adjusted_width
30
- except:
31
- l = col[0].column
32
- if l in fixed:
33
- adjusted_width = fixed["l"]
34
- ws.column_dimensions[l].width = adjusted_width
28
+ col_letter = get_column_letter(col[0].column)
29
+ ws.column_dimensions[col_letter].width = fixed.get(col_letter, adjusted_width)
35
30
 
36
31
 
37
32
  def create_spreadsheet(
38
- headers,
39
- rows,
33
+ headers: List[str],
34
+ rows: List[List],
40
35
  fileorbuffer,
41
- styles={},
42
- merge=[],
43
- formats={},
44
- named_styles=[],
45
- freeze_panes="A2",
46
- dimensions=None,
47
- auto_size=True,
36
+ styles: Dict[str, str] = {},
37
+ merge: List[str] = [],
38
+ formats: Dict[str, str] = {},
39
+ named_styles: List[NamedStyle] = [],
40
+ freeze_panes: str = "A2",
41
+ dimensions: dict = None,
42
+ auto_size: bool = True,
48
43
  ):
44
+ """Create an Excel spreadsheet with specified headers, rows, and styles."""
49
45
  wb = openpyxl.Workbook()
50
46
  ws = wb.active
51
47
 
52
- local_styles = {}
53
-
54
- style = NamedStyle(name="col_header")
55
- style.font = Font(bold=True)
56
- style.border = Border(bottom=Side(style="medium", color="000000"))
57
- local_styles[style.name] = style
58
-
59
- style = NamedStyle(name="sum_total")
60
- style.border = Border(bottom=Side(style="double", color="000000"))
61
- local_styles[style.name] = style
62
-
63
- style = NamedStyle(name="sub_total")
64
- style.font = Font(bold=True)
65
- style.border = Border(bottom=Side(style="thin", color="000000"))
66
- local_styles[style.name] = style
67
-
68
- style = NamedStyle(name="bold")
69
- style.font = Font(bold=True)
70
- local_styles[style.name] = style
71
-
72
- style = NamedStyle(name="align_right")
73
- style.font = Font(bold=True)
74
- style.border = Border(top=Side(style="thin", color="000000"))
75
- style.alignment = Alignment(horizontal="right", vertical="center")
76
- local_styles[style.name] = style
77
-
78
- style = NamedStyle(name="align_left")
79
- style.font = Font(bold=True)
80
- style.border = Border(top=Side(style="thin", color="000000"))
81
- style.alignment = Alignment(horizontal="left", vertical="center")
82
- local_styles[style.name] = style
83
-
84
- style = NamedStyle(name="align_right_double")
85
- style.font = Font(bold=True)
86
- style.border = Border(top=Side(style="double", color="000000"))
87
- style.alignment = Alignment(horizontal="right", vertical="center")
88
- local_styles[style.name] = style
89
-
90
- style = NamedStyle(name="align_left_double")
91
- style.font = Font(bold=True)
92
- style.border = Border(top=Side(style="double", color="000000"))
93
- style.alignment = Alignment(horizontal="left", vertical="center")
94
- local_styles[style.name] = style
95
-
48
+ # Define default named styles
49
+ def get_named_styles():
50
+ named_styles = {
51
+ "col_header": NamedStyle(
52
+ name="col_header",
53
+ font=Font(bold=True),
54
+ border=Border(bottom=Side(style="medium", color="000000")),
55
+ ),
56
+ "sum_total": NamedStyle(
57
+ name="sum_total",
58
+ border=Border(bottom=Side(style="double", color="000000")),
59
+ ),
60
+ "sub_total": NamedStyle(
61
+ name="sub_total",
62
+ font=Font(bold=True),
63
+ border=Border(bottom=Side(style="thin", color="000000")),
64
+ ),
65
+ "bold": NamedStyle(name="bold", font=Font(bold=True)),
66
+ "align_right": NamedStyle(
67
+ name="align_right",
68
+ font=Font(bold=True),
69
+ border=Border(top=Side(style="thin", color="000000")),
70
+ alignment=Alignment(horizontal="right", vertical="center"),
71
+ ),
72
+ "align_left": NamedStyle(
73
+ name="align_left",
74
+ font=Font(bold=True),
75
+ border=Border(top=Side(style="thin", color="000000")),
76
+ alignment=Alignment(horizontal="left", vertical="center"),
77
+ ),
78
+ "align_right_double": NamedStyle(
79
+ name="align_right_double",
80
+ font=Font(bold=True),
81
+ border=Border(top=Side(style="double", color="000000")),
82
+ alignment=Alignment(horizontal="right", vertical="center"),
83
+ ),
84
+ "align_left_double": NamedStyle(
85
+ name="align_left_double",
86
+ font=Font(bold=True),
87
+ border=Border(top=Side(style="double", color="000000")),
88
+ alignment=Alignment(horizontal="left", vertical="center"),
89
+ ),
90
+ }
91
+ return named_styles
92
+
93
+ # Add default and user-defined styles
94
+ local_styles = get_named_styles()
96
95
  for style in named_styles:
97
96
  local_styles[style.name] = style
98
-
99
97
  for style in local_styles.values():
100
98
  wb.add_named_style(style)
101
99
 
100
+ # Add headers and rows
102
101
  ws.append(headers)
102
+ for row in rows:
103
+ ws.append(row)
103
104
 
104
- [ws.append(row) for row in rows]
105
-
106
- if freeze_panes:
107
- ws.freeze_panes = freeze_panes
105
+ # Set freeze panes
106
+ ws.freeze_panes = freeze_panes
108
107
 
108
+ # Auto-size columns if enabled
109
109
  if auto_size:
110
- autosize_columns(ws)
110
+ autosize_columns(ws, fixed={})
111
111
 
112
+ # Set row and column dimensions if provided
112
113
  if dimensions:
113
114
  for key, val in dimensions.get("rows", {}).items():
114
115
  ws.row_dimensions[key].height = val
115
116
  for key, val in dimensions.get("columns", {}).items():
116
117
  ws.column_dimensions[key].width = val
117
118
 
118
- for cell, style in styles.items():
119
- ws[cell].style = style
120
-
119
+ # Apply cell styles, merges, and formats
120
+ for cell, style_name in styles.items():
121
+ if style_name in local_styles:
122
+ ws[cell].style = local_styles[style_name]
121
123
  for cell_range in merge:
122
124
  ws.merge_cells(cell_range)
125
+ for cell, format_code in formats.items():
126
+ ws[cell].number_format = format_code
123
127
 
124
- for cell, format in formats.items():
125
- ws[cell].number_format = format
126
-
128
+ # Save workbook to the provided file or buffer
127
129
  wb.save(fileorbuffer)
128
130
 
129
131
 
130
- def getDownloadableSpreadsheet(
131
- headers,
132
- rows,
133
- styles={},
134
- merge=[],
135
- formats={},
136
- named_styles=[],
137
- freeze_panes="A2",
138
- dimensions=None,
139
- auto_size=True,
140
- ):
132
+ def get_downloadable_spreadsheet(
133
+ headers: List[str],
134
+ rows: List[List],
135
+ styles: Dict[str, str] = {},
136
+ merge: List[str] = [],
137
+ formats: Dict[str, str] = {},
138
+ named_styles: List[NamedStyle] = [],
139
+ freeze_panes: str = "A2",
140
+ dimensions: dict = None,
141
+ auto_size: bool = True,
142
+ ) -> str:
143
+ """Generate a downloadable spreadsheet encoded in base64."""
141
144
  buffer = BytesIO()
142
145
  create_spreadsheet(
143
146
  headers,