toolsos 0.1.4__py3-none-any.whl → 0.2.6__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.
toolsos/cbs_tools.py CHANGED
@@ -2,8 +2,9 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
  import pickle
5
+ from datetime import datetime
5
6
  from pathlib import Path
6
- from typing import TYPE_CHECKING, Iterator, Optional, Any
7
+ from typing import TYPE_CHECKING, Any, Iterator, Optional
7
8
 
8
9
  import pandas as pd
9
10
  import pyarrow as pa
@@ -14,18 +15,27 @@ if TYPE_CHECKING:
14
15
  import pyreadstat
15
16
 
16
17
 
18
+ def get_batch_size(path, memory_limit):
19
+ df, _ = prs.read_sav(path, row_limit=1000)
20
+
21
+ # memory in megabytes
22
+ mem_size = df.memory_usage().sum() / 1_000_000
23
+
24
+ # The amount of blocks (of a thousand rows fit in the memory_limit)
25
+ n_blocks = memory_limit / mem_size
26
+
27
+ # Calculate the number of rows that fit within the memory limit
28
+ return round(n_blocks * 1000)
29
+
30
+
17
31
  class SavToParquet:
18
32
  def __init__(
19
- self,
20
- file: str,
21
- folder_out: str,
22
- chunksize: Optional[int] = None,
23
- verbose: bool = False,
33
+ self, file: str, folder_out: str, verbose: bool = False, memory_limit=10_000
24
34
  ) -> None:
25
35
  self.file = file
26
36
  self.folder_out = folder_out
27
37
  self.verbose = verbose
28
- self.chunksize = 5_000_000 if not chunksize else chunksize
38
+ self.memory_limit = memory_limit
29
39
 
30
40
  @property
31
41
  def path_out(self) -> str:
@@ -33,20 +43,27 @@ class SavToParquet:
33
43
 
34
44
  @property
35
45
  def chunks(self) -> Iterator[tuple["pyreadstat.metadata_container", pd.DataFrame]]:
36
- return prs.read_file_in_chunks(
37
- prs.read_sav, self.file, chunksize=self.chunksize
38
- )
39
46
 
40
- def get_meta(self) -> Iterator:
41
- return prs.read_sav(self.file, row_limit=10)
47
+ chunksize = get_batch_size(self.file, self.memory_limit)
48
+
49
+ if self.verbose:
50
+ print(f"Reading file in blocks of {chunksize} rows")
51
+ print("One such block should fit within the memory limit")
52
+
53
+ return prs.read_file_in_chunks(prs.read_sav, self.file, chunksize=chunksize)
42
54
 
43
55
  def write_meta_to_json(self) -> None:
44
56
  json_path = self.path_out.replace(".parquet", "_meta.json")
45
57
 
46
58
  meta_dict = {}
47
- for attr in dir(self.meta):
48
- if not attr.startswith("__"):
49
- meta_dict[attr] = getattr(self.meta, attr)
59
+ for attr_name in dir(self.meta):
60
+ if not attr_name.startswith("__"):
61
+ attr = getattr(self.meta, attr_name)
62
+
63
+ if isinstance(attr, datetime):
64
+ attr = attr.strftime("%Y-%m-%d %H:%M:%S")
65
+
66
+ meta_dict[attr_name] = attr
50
67
 
51
68
  with open(json_path, "w") as file:
52
69
  json.dump(meta_dict, file)
@@ -58,10 +75,12 @@ class SavToParquet:
58
75
  pickle.dump(self.meta, file)
59
76
 
60
77
  def write_to_parquet(self) -> None:
61
- meta_df, self.meta = self.get_meta()
62
- schema = table = pa.Table.from_pandas(meta_df).schema
63
78
 
64
79
  print("Writing table")
80
+
81
+ line1, self.meta = prs.read_sav(self.file, row_limit=1)
82
+ schema = pa.Table.from_pandas(line1).schema
83
+
65
84
  with pq.ParquetWriter(self.path_out, schema) as writer:
66
85
  for idx, (df, _) in enumerate(self.chunks):
67
86
  if self.verbose:
@@ -2,8 +2,10 @@ from __future__ import annotations
2
2
 
3
3
  import getpass
4
4
  import json
5
+ import os
5
6
  import subprocess
6
7
  from json import JSONDecodeError
8
+ from pathlib import Path
7
9
  from typing import Optional
8
10
 
9
11
  import keyring
@@ -33,7 +35,7 @@ def get_db_connection_strings(
33
35
  for dbname, params in db_info.items():
34
36
  flush = dbname in reset_pw if reset_pw else False
35
37
 
36
- if params["pw"] == "acces_token":
38
+ if params["password"] == "access_token":
37
39
  pw = get_azure_access_token()
38
40
  else:
39
41
  pw = get_pw_from_keyring(dbname=dbname, user=params["user"], reset_pw=flush)
@@ -94,21 +96,60 @@ def get_azure_access_token():
94
96
  result = subprocess.run(command, capture_output=True, shell=True, text=True)
95
97
 
96
98
  try:
97
- json.loads(result.stdout)["accessToken"]
99
+ return json.loads(result.stdout)["accessToken"]
98
100
  except JSONDecodeError:
99
101
  subprocess.run("az login", shell=True)
100
102
 
101
103
 
102
- if __name__ == "__main__":
103
- ...
104
- # Examples
104
+ def get_token_from_pgpass() -> None:
105
+ p = Path(os.getenv("APPDATA")) / "postgresql" / "pgpass.conf"
106
+ with open(p) as f:
107
+ token = f.readline().split(":")[4]
105
108
 
106
- # Get database connection settings from yaml
107
- # engine_strings = get_db_connection_strings("python/database_config.yml")
108
- # print(engine_strings.ruimte_analyse222)
109
+ return token
109
110
 
110
- # Get database connection settings from yaml and reset password
111
- # engine_strings = get_db_connection_strings(
112
- # "python/database_config.yml", reset_pw=["ruimte_analyse222"]
113
- # )
114
- # print(engine_strings.ruimte_analyse222)
111
+
112
+ def write_pgpass(
113
+ host: str, port: str, database: str, user: str, path: str | None = None
114
+ ) -> None:
115
+ password = get_azure_access_token()
116
+ conn_string = f"{host}:{port}:{database}:{user}:{password}"
117
+
118
+ if not path:
119
+ if os.name == "nt":
120
+ path = Path(os.getenv("APPDATA")) / "postgresql" / "pgpass.conf"
121
+ else:
122
+ path = Path("$home/.pgpass")
123
+
124
+ if not path.parent.exists():
125
+ path.parent.mkdir()
126
+
127
+ with open(path, "w") as f:
128
+ f.write(conn_string)
129
+
130
+ if os.name != "nt":
131
+ path.chmod("0600")
132
+
133
+
134
+ def write_multiple_pgpass(conn_details, path: str | None = None):
135
+ password = get_azure_access_token()
136
+
137
+ conn_strings = []
138
+ for c in conn_details:
139
+ c_string = f'{c["host"]}:{c["port"]}:{c["database"]}:{c["user"]}:{password}'
140
+ conn_strings.append(c_string)
141
+
142
+ if not path:
143
+ if os.name == "nt":
144
+ path = Path(os.getenv("APPDATA")) / "postgresql" / "pgpass.conf"
145
+ else:
146
+ path = Path("$home/.pgpass")
147
+
148
+ if not path.parent.exists():
149
+ path.parent.mkdir()
150
+
151
+ with open(path, "w") as f:
152
+ f.writelines(line + "\n" for line in conn_strings)
153
+
154
+ if os.name != "nt":
155
+ path.chmod("0600")
toolsos/geo.py CHANGED
@@ -17,7 +17,7 @@ def get_geo_json(
17
17
  Returns:
18
18
  dict[str, str]: geo json containg of the desired level and year
19
19
  """
20
- base_url = "https://gitlab.com/os-amsterdam/datavisualisatie-onderzoek-en-statistiek/-/raw/main/geo/"
20
+ base_url = "https://gitlab.com/os-amsterdam/datavisualisatie-onderzoek-en-statistiek/-/raw/main/public/geo/"
21
21
 
22
22
  if mra:
23
23
  level = f"{level}-mra"
@@ -1,4 +1,5 @@
1
1
  import plotly.express as px
2
+
2
3
  from .styler import BaseStyle
3
4
 
4
5
  basestyle = BaseStyle()
@@ -14,6 +15,7 @@ def bar(
14
15
  barmode=None,
15
16
  width=750,
16
17
  height=490,
18
+ font="Amsterdam Sans",
17
19
  **kwargs,
18
20
  ):
19
21
  fig = px.bar(
@@ -21,7 +23,7 @@ def bar(
21
23
  x=x,
22
24
  y=y,
23
25
  color=color,
24
- template=basestyle.get_base_template("bar", orientation=orientation),
26
+ template=basestyle.get_base_template("bar", orientation=orientation, font=font),
25
27
  width=width,
26
28
  color_discrete_sequence=color_discrete_sequence,
27
29
  height=height,
@@ -43,6 +45,7 @@ def stacked_single(
43
45
  color: str = None,
44
46
  color_discrete_sequence: list = None,
45
47
  orientation="v",
48
+ font="Amsterdam Sans",
46
49
  **kwargs,
47
50
  ):
48
51
  fig = bar(
@@ -53,6 +56,7 @@ def stacked_single(
53
56
  color_discrete_sequence=color_discrete_sequence,
54
57
  barmode="relative",
55
58
  orientation=orientation,
59
+ font=font,
56
60
  **kwargs,
57
61
  )
58
62
 
@@ -71,6 +75,7 @@ def stacked_multiple(
71
75
  color: str = None,
72
76
  color_discrete_sequence: list = None,
73
77
  orientation="v",
78
+ font="Amsterdam Sans",
74
79
  **kwargs,
75
80
  ):
76
81
  fig = bar(
@@ -81,6 +86,7 @@ def stacked_multiple(
81
86
  color_discrete_sequence=color_discrete_sequence,
82
87
  barmode="stack",
83
88
  orientation=orientation,
89
+ font=font,
84
90
  **kwargs,
85
91
  )
86
92
 
@@ -94,6 +100,7 @@ def grouped(
94
100
  color: str = None,
95
101
  color_discrete_sequence: list = None,
96
102
  orientation="v",
103
+ font="Amsterdam Sans",
97
104
  **kwargs,
98
105
  ):
99
106
  fig = bar(
@@ -104,6 +111,7 @@ def grouped(
104
111
  color_discrete_sequence=color_discrete_sequence,
105
112
  barmode="group",
106
113
  orientation=orientation,
114
+ font=font,
107
115
  **kwargs,
108
116
  )
109
117
 
@@ -116,6 +124,7 @@ def single(
116
124
  y: str,
117
125
  color_discrete_sequence: list = None,
118
126
  orientation="v",
127
+ font="Amsterdam Sans",
119
128
  **kwargs,
120
129
  ):
121
130
  fig = bar(
@@ -124,6 +133,7 @@ def single(
124
133
  y=y,
125
134
  color_discrete_sequence=color_discrete_sequence,
126
135
  orientation=orientation,
136
+ font=font,
127
137
  **kwargs,
128
138
  )
129
139
 
@@ -1,5 +1,8 @@
1
+ font = "Amsterdam Sans ExtraBold, Corbel"
2
+ # font = "Arial"
3
+
1
4
  STYLE_OLD = {
2
- "font": {"family": "Corbel", "font_size": 15},
5
+ "font": {"family": "Corbel", "size": 15},
3
6
  "plot_bgcolor": "#FFFFFF",
4
7
  "gridline_color": "#E8E8E8",
5
8
  "gridlinewidth": 0.75,
@@ -12,13 +15,18 @@ STYLE_OLD = {
12
15
 
13
16
 
14
17
  STYLE_NEW = {
15
- "font": "Amsterdam Sans ExtraBold, Corbel",
16
- "font_size": 15,
18
+ "font_bold": {"family": "Amsterdam Sans ExtraBold, Corbel", "size": 15},
19
+ "font_bold_corbel": {"family": "Corbel Bold", "size": 15},
20
+ "font": {"family": "Amsterdam Sans, Corbel", "size": 15},
21
+ "font_corbel": {"family": "Corbel", "size": 15},
22
+ "axis_font": {"family": "Amsterdam Sans ExtraBold, Corbel", "size": 15},
17
23
  "plot_bgcolor": "#FFFFFF",
18
- "gridline_color": "#E8E8E8",
19
- "gridlinewidth": 0.75,
24
+ "gridline_color": "#dbdbdb",
25
+ "gridline_width": 0.75,
20
26
  "showline": True,
21
27
  "showgrid": False,
22
- "zerolinecolor": "#E8E8E8",
23
- "gridcolor": "#E8E8E8",
28
+ "zerolinecolor": "#dbdbdb",
29
+ "gridcolor": "#dbdbdb",
30
+ "gridlinecolor": "#dbdbdb",
31
+ "separators": ",",
24
32
  }
@@ -1,11 +1,21 @@
1
1
  import plotly.express as px
2
- from .styler import BaseStyle
3
2
 
3
+ from .styler import BaseStyle
4
4
 
5
5
  basestyle = BaseStyle()
6
6
 
7
7
 
8
- def line(data, x, y, color: None, width=750, height=490, **kwargs):
8
+ def line(
9
+ data,
10
+ x,
11
+ y,
12
+ color: None,
13
+ width=750,
14
+ height=490,
15
+ color_discrete_sequence=None,
16
+ font="Amsterdam Sans",
17
+ **kwargs,
18
+ ):
9
19
  fig = px.line(
10
20
  data_frame=data,
11
21
  x=x,
@@ -13,8 +23,13 @@ def line(data, x, y, color: None, width=750, height=490, **kwargs):
13
23
  color=color,
14
24
  width=width,
15
25
  height=height,
16
- template=BaseStyle().get_base_template(graph_type="line"),
26
+ color_discrete_sequence=color_discrete_sequence,
27
+ template=BaseStyle().get_base_template(graph_type="line", font=font),
17
28
  **kwargs,
18
29
  )
19
30
 
31
+ fig.update_layout(
32
+ dict(xaxis_title_text="", yaxis_title_text="", legend_title_text="")
33
+ )
34
+
20
35
  return fig
@@ -1,4 +1,5 @@
1
1
  import plotly.express as px
2
+
2
3
  from .styler import BaseStyle
3
4
 
4
5
  basestyle = BaseStyle()
@@ -12,6 +13,8 @@ def pie(
12
13
  width=750,
13
14
  height=490,
14
15
  text_format: str = None,
16
+ color_discrete_sequence=None,
17
+ font="Amsterdam Sans",
15
18
  **kwargs,
16
19
  ):
17
20
  fig = px.pie(
@@ -21,7 +24,8 @@ def pie(
21
24
  width=width,
22
25
  height=height,
23
26
  hole=hole,
24
- template=BaseStyle().get_base_template(),
27
+ template=BaseStyle().get_base_template(font=font),
28
+ color_discrete_sequence=color_discrete_sequence,
25
29
  **kwargs,
26
30
  )
27
31
 
@@ -4,56 +4,153 @@ import plotly.graph_objects as go
4
4
  import requests
5
5
  from requests import ConnectionError
6
6
 
7
+ from .graph_styles import STYLE_NEW
8
+
9
+ # class BaseStyle:
10
+ # style_url = (
11
+ # "https://raw.githubusercontent.com/jbosga-ams/oistyle/main/base_style.json"
12
+ # )
13
+
14
+ # def __init__(self):
15
+ # self.grab_styling()
16
+
17
+ # def grab_styling(self, style_path: str = None):
18
+ # if not style_path:
19
+ # try:
20
+ # res = requests.get(self.style_url).json()
21
+ # except ConnectionError:
22
+ # print("Failed grabbing basestyle from the interwebs")
23
+ # # Add option to manually provide json file
24
+ # else:
25
+ # res = json.loads()
26
+
27
+ # for k, v in res.items():
28
+ # setattr(self, k, v)
29
+
30
+ # self.font = "Amsterdam Sans ExtraBold, Corbel"
31
+
32
+ # def _get_axis_format(self):
33
+ # self.gridline_color = "#dbdbdb" # Jorren vragen om deze aan te passen
34
+
35
+ # return {
36
+ # "zerolinecolor": self.gridline_color,
37
+ # "gridcolor": self.gridline_color,
38
+ # "gridwidth": self.gridline_width,
39
+ # "showline": True,
40
+ # "linewidth": self.gridline_width,
41
+ # "linecolor": self.gridline_color,
42
+ # # "mirror": True,
43
+ # "showgrid": False,
44
+ # }
45
+
46
+ # def _get_base_template_layout(self):
47
+ # return go.layout.Template(
48
+ # layout={
49
+ # "font": {"family": self.font, "size": self.font_size},
50
+ # "plot_bgcolor": self.plot_bgcolor,
51
+ # "colorway": self.colors["darkblue_lightblue_gradient_5"],
52
+ # "separators": ",", # Jorren vragen om deze toe te voegen
53
+ # }
54
+ # )
55
+
56
+ # def get_base_template(
57
+ # self, graph_type: str = None, orientation: str = None, colors: str = None
58
+ # ):
59
+ # """[summary]
60
+
61
+ # Args:
62
+ # graph_type (str, optional): Pick 'bar', 'line' or 'bar'. Defaults to None.
63
+ # orientation (str, optional): [description]. Pick horizontal ('h') or vertical 'v'. Defaults to None.
64
+ # colors (str, optional): Set basecolors. Defaults to None.
65
+
66
+ # Raises:
67
+ # ValueError: [description]
68
+
69
+ # Returns:
70
+ # [type]: [description]
71
+ # """
72
+ # base_template = self._get_base_template_layout()
73
+ # axis_format = self._get_axis_format()
74
+
75
+ # if graph_type == "bar":
76
+ # if orientation in ["v", "vertical"]:
77
+ # base_template.layout.xaxis.update(axis_format)
78
+ # base_template.layout.yaxis.update(zeroline=False)
79
+ # elif orientation in ["h", "horizontal"]:
80
+ # base_template.layout.yaxis.update(axis_format)
81
+ # base_template.layout.xaxis.update(zeroline=False)
82
+ # else:
83
+ # raise ValueError(
84
+ # "Orientation ('v'/'vertical' or 'h'/'horizontal') should be supplied with graph_type=='bar'"
85
+ # )
86
+
87
+ # elif graph_type == "line":
88
+ # base_template.layout.xaxis.update(axis_format)
89
+
90
+ # if colors:
91
+ # base_template.layout.update({"colorway": colors})
92
+
93
+ # return base_template
94
+
95
+ # def get_ois_colors(self, colorscale):
96
+ # colorscale = self.colors.get(colorscale, [])
97
+ # if not colorscale:
98
+ # raise Exception(f"Kies uit {self.colors.keys()}")
99
+
100
+ # return colorscale
101
+
7
102
 
8
103
  class BaseStyle:
9
- style_url = (
10
- "https://raw.githubusercontent.com/jbosga-ams/oistyle/main/base_style.json"
11
- )
12
-
13
- def __init__(self):
14
- self.grab_styling()
15
-
16
- def grab_styling(self, style_path: str = None):
17
- if not style_path:
18
- try:
19
- res = requests.get(self.style_url).json()
20
- except ConnectionError:
21
- print("Failed grabbing basestyle from the interwebs")
22
- # Add option to manually provide json file
104
+ def __init__(self, style_path=None):
105
+ if style_path is None:
106
+ self.style = STYLE_NEW
23
107
  else:
24
- res = json.loads()
25
-
26
- for k, v in res.items():
27
- setattr(self, k, v)
28
-
29
- self.font = "Amsterdam Sans ExtraBold, Corbel"
108
+ with open(style_path) as file:
109
+ self.style = json.load(file)
30
110
 
31
111
  def _get_axis_format(self):
32
- self.gridline_color = "#dbdbdb" # Jorren vragen om deze aan te passen
33
112
 
34
113
  return {
35
- "zerolinecolor": self.gridline_color,
36
- "gridcolor": self.gridline_color,
37
- "gridwidth": self.gridline_width,
114
+ "zerolinecolor": self.style["gridline_color"],
115
+ "gridcolor": self.style["gridline_color"],
116
+ "gridwidth": self.style["gridline_width"],
38
117
  "showline": True,
39
- "linewidth": self.gridline_width,
40
- "linecolor": self.gridline_color,
41
- # "mirror": True,
42
- "showgrid": False,
118
+ "linewidth": self.style["gridline_width"],
119
+ "linecolor": self.style["gridline_color"],
120
+ "showgrid": self.style["showgrid"],
43
121
  }
44
122
 
45
- def _get_base_template_layout(self):
123
+ def _get_base_template_layout(self, font):
124
+ if font == "Amsterdam Sans":
125
+ font_ = self.style["font"]
126
+ font_bold_ = self.style["font_bold"]
127
+ elif font == "Corbel":
128
+ font_ = self.style["font_corbel"]
129
+ font_bold_ = self.style["font_bold_corbel"]
130
+ else:
131
+ raise ValueError("Font should be 'Amsterdam Sans' or 'Corbel'")
132
+
46
133
  return go.layout.Template(
47
134
  layout={
48
- "font": {"family": self.font, "size": self.font_size},
49
- "plot_bgcolor": self.plot_bgcolor,
50
- "colorway": self.colors["darkblue_lightblue_gradient_5"],
51
- "separators": ",", # Jorren vragen om deze toe te voegen
135
+ "xaxis": {
136
+ "tickfont": font_bold_,
137
+ },
138
+ "yaxis": {
139
+ "tickfont": font_bold_,
140
+ },
141
+ "legend": {"font": font_},
142
+ "plot_bgcolor": self.style["plot_bgcolor"],
143
+ "separators": ",",
144
+ "font": font_bold_,
52
145
  }
53
146
  )
54
147
 
55
148
  def get_base_template(
56
- self, graph_type: str = None, orientation: str = None, colors: str = None
149
+ self,
150
+ graph_type: str = None,
151
+ orientation: str = None,
152
+ colors: str = None,
153
+ font: str = "Amsterdam Sans",
57
154
  ):
58
155
  """[summary]
59
156
 
@@ -68,7 +165,7 @@ class BaseStyle:
68
165
  Returns:
69
166
  [type]: [description]
70
167
  """
71
- base_template = self._get_base_template_layout()
168
+ base_template = self._get_base_template_layout(font)
72
169
  axis_format = self._get_axis_format()
73
170
 
74
171
  if graph_type == "bar":
@@ -90,14 +187,3 @@ class BaseStyle:
90
187
  base_template.layout.update({"colorway": colors})
91
188
 
92
189
  return base_template
93
-
94
- def get_ois_colors(self, colorscale):
95
- colorscale = self.colors.get(colorscale, [])
96
- if not colorscale:
97
- raise Exception(f"Kies uit {self.colors.keys()}")
98
-
99
- return colorscale
100
-
101
-
102
- class Basestyle2:
103
- def __init__(self, style): ...
@@ -0,0 +1,76 @@
1
+ from pathlib import Path
2
+
3
+ import pandas as pd
4
+ import win32com.client as win32
5
+
6
+
7
+ def remove_underscores_from_columns(df: pd.DataFrame) -> pd.DataFrame:
8
+ df.columns = df.columns.str.replace("_", " ")
9
+ return df
10
+
11
+
12
+ def get_excel_files_from_folder(folder: str) -> list[str]:
13
+ return [str(f.resolve()) for f in Path("to_merge").glob("*") if f.suffix == ".xlsx"]
14
+
15
+
16
+ def combine_excel_files(out_path: str, files: list[str] = None, overwrite: bool = True):
17
+ out_path = Path(out_path)
18
+
19
+ if overwrite:
20
+ if out_path.exists():
21
+ out_path.unlink()
22
+
23
+ # INITIALIZE EXCEL COM APP
24
+ try:
25
+ xlapp = win32.gencache.EnsureDispatch("Excel.Application")
26
+
27
+ # constants
28
+ xlPasteValues = -4163
29
+ lPasteFormats = -4122
30
+ xlWorkbookDefault = 51
31
+
32
+ # create new workbook
33
+ new_wb = xlapp.Workbooks.Add()
34
+ new_wb.SaveAs(Filename=str(out_path), FileFormat=xlWorkbookDefault)
35
+
36
+ dup_count = 1
37
+
38
+ for wb in files:
39
+ xlwb = xlapp.Workbooks.Open(wb)
40
+
41
+ for xlsh in xlwb.Worksheets:
42
+ new_sh = new_wb.Worksheets.Add()
43
+
44
+ try:
45
+ new_sh.Name = xlsh.Name
46
+
47
+ # Ugly non defined exception. Be aware that this wil caputre
48
+ except Exception as e:
49
+ new_sh.Name = f"{xlsh.Name}_{dup_count}"
50
+ dup_count += 1
51
+
52
+ new_wb.Save()
53
+ new_sh.Move(After=new_wb.Worksheets(new_wb.Worksheets.Count))
54
+
55
+ xlsh.Cells.Copy(new_sh.Cells)
56
+ new_sh = None
57
+
58
+ xlwb.Close(True)
59
+ xlwb = None
60
+
61
+ # remove default blad1
62
+ new_wb.Worksheets("Blad1").Delete()
63
+ new_wb.Save()
64
+
65
+ except Exception as e:
66
+ print(e)
67
+
68
+ # RELEASE RESOURCES
69
+ finally:
70
+ xlsh = None
71
+ new_sh = None
72
+ xlwb = None
73
+ new_wb = None
74
+ xlapp.Quit()
75
+ xlapp = None
76
+ xlwb = None
@@ -46,6 +46,38 @@ def set_global_style(style: str) -> None:
46
46
  STYLES = STYLE_NEW
47
47
 
48
48
 
49
+ def cols_to_str(df: pd.DataFrame) -> pd.DataFrame:
50
+ """Change column names in to string. Multiindex column names are nog changed because
51
+ these are always strings
52
+
53
+ Args:
54
+ df (pd.DataFrame): Dataframe
55
+
56
+ Returns:
57
+ pd.DataFrame: Dataframe with column names as strings
58
+ """
59
+
60
+ # Multiindex columns are always strings and therefore can't be casted as string
61
+ if df.columns.nlevels == 1:
62
+ df.columns = df.columns.astype(str)
63
+
64
+ return df
65
+
66
+
67
+ def get_max_col_widths(data: pd.DataFrame | np.ndarray) -> list[float]:
68
+ col_widths = []
69
+ if isinstance(data, pd.DataFrame):
70
+ for col in zip(*flatten_multiindex_columns(data)):
71
+ col_widths.append(max(len(e) for e in col))
72
+ else:
73
+ for col in zip(*data):
74
+ col_widths.append(max(len(str(e)) for e in col))
75
+
76
+ col_widths = [col_width * 1.13 for col_width in col_widths]
77
+
78
+ return col_widths
79
+
80
+
49
81
  def flatten_multiindex_columns(df):
50
82
  column_multi = []
51
83
  for level in range(df.columns.nlevels):
@@ -67,20 +99,32 @@ def df_to_array(df: pd.DataFrame) -> np.ndarray:
67
99
  return np.vstack([column_names, df.to_numpy()])
68
100
 
69
101
 
70
- def get_cells_to_merge(df, n_levels=1):
71
- levels = flatten_multiindex_columns(df)
72
- levels = levels[0:n_levels]
102
+ def get_cells_to_merge(df: pd.DataFrame) -> dict[int : list[int, int]]:
103
+ """Pandas dataframes sometimes have mutliindex columns. For all but the last level
104
+ a dictionary is created to merge the cells. The last level isn't merged because these
105
+ are these contain unique column names
73
106
 
74
- merge_cells = []
75
- start_col = 1
107
+ Args:
108
+ df (pd.DataFrame): Pandas dataframe. If the dataframe has multicolumn indices
109
+ a dictionary containg the cells to merge is returned
110
+
111
+ Returns:
112
+ dict[int: list[int, int]]: Dictionary containg the cells to merge
113
+ """
114
+ levels = flatten_multiindex_columns(df)[:-1]
76
115
 
77
- for level in levels:
116
+ cells_to_merge = {}
117
+ for level_idx, level in enumerate(levels):
118
+ start_col = 1
119
+ start_col = 1
120
+ merge_cells = []
78
121
  for _, val in groupby(level):
79
122
  val = list(val)
80
123
  merge_cells.append([start_col, start_col + len(val) - 1])
81
124
  start_col += len(val)
125
+ cells_to_merge[level_idx] = merge_cells
82
126
 
83
- return merge_cells
127
+ return cells_to_merge
84
128
 
85
129
 
86
130
  def get_fmt_table(arr: np.ndarray) -> Fmt:
@@ -329,14 +373,15 @@ def cell_formatting(
329
373
  return fmt
330
374
 
331
375
 
332
- def write_worksheet(
376
+ def write_to_worksheet(
333
377
  ws: Any,
334
378
  arr: np.ndarray,
335
379
  fmt: Fmt,
336
380
  title: str | None = None,
337
381
  source: str | None = None,
338
382
  col_filter: bool | None = None,
339
- autofit_columns: bool | None = None,
383
+ col_widths: list | None = None,
384
+ min_column_width: int | None = None,
340
385
  cells_to_merge: list[list[int]] | None = None,
341
386
  ) -> None:
342
387
  """Writing data to worksheet. Used for writing values to cells and formatting the cells
@@ -377,51 +422,61 @@ def write_worksheet(
377
422
  filters = ws.auto_filter
378
423
  filters.ref = f"A1:{excel_style(len(fmt), len(fmt[0]))}"
379
424
 
380
- if autofit_columns:
381
- _autofit_columns(ws)
382
-
383
425
  if source:
384
426
  _insert_source(ws, source, arr)
385
427
 
428
+ if col_widths:
429
+ _set_column_widths(ws, col_widths, min_column_width)
430
+
386
431
  if title:
387
432
  _insert_title(ws, title)
388
433
 
389
434
  if cells_to_merge:
390
- _merge_cells(ws, cells_to_merge)
435
+ _merge_cells(ws, cells_to_merge, title)
391
436
 
392
437
 
393
- # def _set_column_width(ws: Any, column_widths: list) -> None:
394
- # for i, column_number in enumerate(range(ws.max_column)):
395
- # column_letter = get_column_letter(column_letter)
396
- # column_width = column_widths[i]
397
- # ws.column_dimensions[column_letter].width = column_width
438
+ def _set_column_widths(
439
+ ws: Any, col_widths: list[int], min_column_width: int | None
440
+ ) -> None:
441
+ for idx, col_width in enumerate(col_widths):
442
+ col_letter = get_column_letter(idx + 1)
398
443
 
444
+ if min_column_width:
445
+ if col_width < min_column_width:
446
+ col_width = min_column_width
447
+
448
+ ws.column_dimensions[col_letter].width = col_width
399
449
 
400
- def _autofit_columns(ws: Any) -> None:
401
- column_letters = tuple(
402
- get_column_letter(col_number + 1) for col_number in range(ws.max_column)
403
- )
404
- for column_letter in column_letters:
405
- ws.column_dimensions[column_letter].auto_fit = True
406
450
 
451
+ def _merge_cells(ws, cells_to_merge, title: str | None = None) -> None:
452
+ add = 0
453
+ if title:
454
+ add = 1
407
455
 
408
- def _merge_cells(ws, cells_to_merge):
409
- for start, stop in cells_to_merge:
410
- cell = ws.cell(1, start)
411
- cell.alignment = Alignment(horizontal="left")
412
- ws.merge_cells(start_row=1, end_row=1, start_column=start, end_column=stop)
456
+ for row_idx, merge in cells_to_merge.items():
457
+ row_idx = row_idx + add
458
+ for start, stop in merge:
459
+ cell = ws.cell(row_idx + 1, start)
460
+ cell.alignment = Alignment(horizontal="center")
461
+ ws.merge_cells(
462
+ start_row=row_idx + 1,
463
+ end_row=row_idx + 1,
464
+ start_column=start,
465
+ end_column=stop,
466
+ )
413
467
 
414
468
 
415
469
  def _insert_source(ws, source, arr):
416
470
  height, width = arr.shape
417
- cell = ws.cell(width, height + 1, source)
418
- cell.font = Font(**STYLES["calibri"]["font"])
471
+ cell = ws.cell(height + 1, width, source)
419
472
  cell.alignment = Alignment(horizontal="right")
473
+ cell.font = Font(**STYLES["calibri"]["font"])
420
474
 
421
475
 
422
476
  def _insert_title(ws: Any, title: str) -> None:
423
477
  ws.insert_rows(0)
424
478
  cell = ws.cell(1, 1, title)
479
+ cell.alignment = Alignment(horizontal="left")
425
480
  for t, kwa in STYLES["title_bold"].items():
426
481
  setattr(cell, t, LOOKUP[t](**kwa))
427
482
 
@@ -447,10 +502,99 @@ def write_table(
447
502
  blue_border: bool | None = True,
448
503
  blue_border_row_ids: int | list[int] | None = None,
449
504
  number_format: str = "0.0",
450
- autofit_columns: bool | None = False,
505
+ autofit_columns: str | None = "column_names",
506
+ min_column_width: int | None = None,
451
507
  col_filter: bool | None = False,
452
508
  style: str = "old",
453
- combine_multiindex_cols: bool | int = False,
509
+ combine_multiindex: bool | int = False,
510
+ column_names_to_string: bool = True,
511
+ ):
512
+ wb = Workbook()
513
+ # Empty sheet is created on Workbook creation
514
+ del wb["Sheet"]
515
+
516
+ set_global_style(style)
517
+
518
+ if not isinstance(data, dict):
519
+ data = {"Sheet1": data}
520
+
521
+ for sheet_name, df in data.items():
522
+ format_worksheet(
523
+ wb=wb,
524
+ df=df,
525
+ sheet_name=sheet_name,
526
+ header_row=header_row,
527
+ title=title,
528
+ source=source,
529
+ total_row=total_row,
530
+ light_blue_row_ids=light_blue_row_ids,
531
+ total_col=total_col,
532
+ right_align_ids=right_align_ids,
533
+ right_align_pattern=right_align_pattern,
534
+ right_align_numeric=right_align_numeric,
535
+ left_align_ids=left_align_ids,
536
+ left_align_pattern=left_align_pattern,
537
+ left_align_string=left_align_string,
538
+ perc_ids=perc_ids,
539
+ perc_pattern=perc_pattern,
540
+ perc_col_format=perc_col_format,
541
+ blue_border=blue_border,
542
+ blue_border_row_ids=blue_border_row_ids,
543
+ number_format=number_format,
544
+ autofit_columns=autofit_columns,
545
+ min_column_width=min_column_width,
546
+ col_filter=col_filter,
547
+ combine_multiindex=combine_multiindex,
548
+ column_names_to_string=column_names_to_string,
549
+ )
550
+
551
+ wb.save(file)
552
+
553
+
554
+ def write_table_from_dict(
555
+ file,
556
+ write_info,
557
+ style: str = "old",
558
+ ):
559
+ wb = Workbook()
560
+ # Empty sheet is created on Workbook creation
561
+ del wb["Sheet"]
562
+
563
+ set_global_style(style)
564
+
565
+ for sheet in write_info:
566
+ format_worksheet(wb=wb, **sheet)
567
+
568
+ wb.save(file)
569
+
570
+
571
+ def format_worksheet(
572
+ wb: Any,
573
+ df: pd.DataFrame,
574
+ sheet_name: str,
575
+ header_row: int = 0,
576
+ title: str | dict[str, str] | None = None,
577
+ source: str | None = None,
578
+ total_row: bool | None = None,
579
+ light_blue_row_ids: int | list[int] | None = None,
580
+ total_col: bool | None = None,
581
+ right_align_ids: list | None = None,
582
+ right_align_pattern: str | None = None,
583
+ right_align_numeric: bool | None = True,
584
+ left_align_ids: list | None = None,
585
+ left_align_pattern: str | None = None,
586
+ left_align_string: bool | None = True,
587
+ perc_ids: list | None = None,
588
+ perc_pattern: str | None = None,
589
+ perc_col_format: str | None = None,
590
+ blue_border: bool | None = True,
591
+ blue_border_row_ids: int | list[int] | None = None,
592
+ number_format: str = "0.0",
593
+ autofit_columns: str | None = "column_names",
594
+ min_column_width: int | None = None,
595
+ col_filter: bool | None = False,
596
+ combine_multiindex: bool | int = False,
597
+ column_names_to_string: bool = True,
454
598
  ):
455
599
  """_summary_
456
600
 
@@ -473,107 +617,112 @@ def write_table(
473
617
  perc_col_format (str, optional): The formatting string of percentage columns. Defaults to None.
474
618
  col_filter (bool, optional): Set filter on columns. Defaults to False.
475
619
  """
620
+ if column_names_to_string == True:
621
+ df = cols_to_str(df)
476
622
 
477
- wb = Workbook()
478
- # Empty sheet is created on Workbook creation
479
- del wb["Sheet"]
480
-
481
- set_global_style(style)
623
+ arr = df_to_array(df)
482
624
 
483
- if not isinstance(data, dict):
484
- data = {"Sheet1": data}
625
+ blue_rows = []
626
+ light_blue_rows = []
627
+ light_blue_cols = []
628
+ blue_border_ids = []
629
+ r_align_ids = []
630
+ l_align_ids = []
631
+ p_ids = []
632
+ cells_to_merge = []
633
+ title_tbl = None
634
+ title_src = None
485
635
 
486
- for sheet_name, df in data.items():
487
- arr = df_to_array(df)
636
+ if isinstance(header_row, int):
637
+ blue_rows.extend(list(range(0, header_row + 1)))
488
638
 
489
- blue_rows = []
490
- light_blue_rows = []
491
- light_blue_cols = []
492
- blue_border_ids = []
493
- r_align_ids = []
494
- l_align_ids = []
495
- p_ids = []
496
- cells_to_merge = []
497
- title_tbl = None
498
-
499
- if isinstance(header_row, int):
500
- blue_rows.extend(list(range(0, header_row + 1)))
501
-
502
- if title:
503
- if isinstance(title, str):
504
- title_tbl = title
505
- elif isinstance(title, dict):
506
- title_tbl = title.get(sheet_name)
507
-
508
- if right_align_ids:
509
- r_align_ids.extend(right_align_ids)
639
+ if title:
640
+ if isinstance(title, str):
641
+ title_tbl = title
642
+ elif isinstance(title, dict):
643
+ title_tbl = title.get(sheet_name)
510
644
 
511
- if right_align_pattern:
512
- r_align_ids.extend(get_cols_id_with_pattern(df, right_align_pattern))
645
+ if source:
646
+ if isinstance(source, str):
647
+ title_src = source
648
+ elif isinstance(title, dict):
649
+ title_src = source.get(sheet_name)
513
650
 
514
- if right_align_numeric:
515
- r_align_ids.extend(get_numeric_col_ids(df))
651
+ if right_align_ids:
652
+ r_align_ids.extend(right_align_ids)
516
653
 
517
- if left_align_ids:
518
- r_align_ids.extend(left_align_ids)
654
+ if right_align_pattern:
655
+ r_align_ids.extend(get_cols_id_with_pattern(df, right_align_pattern))
519
656
 
520
- if left_align_pattern:
521
- l_align_ids.extend(get_cols_id_with_pattern(df, left_align_pattern))
657
+ if right_align_numeric:
658
+ r_align_ids.extend(get_numeric_col_ids(df))
522
659
 
523
- if left_align_string:
524
- l_align_ids.extend(get_string_cols_ids(df))
660
+ if left_align_ids:
661
+ r_align_ids.extend(left_align_ids)
525
662
 
526
- if perc_ids:
527
- p_ids.extend(perc_ids)
663
+ if left_align_pattern:
664
+ l_align_ids.extend(get_cols_id_with_pattern(df, left_align_pattern))
528
665
 
529
- if perc_pattern:
530
- r_id = get_cols_id_with_pattern(df, perc_pattern)
531
- p_ids.extend(r_id)
532
- r_align_ids.extend(r_id)
666
+ if left_align_string:
667
+ l_align_ids.extend(get_string_cols_ids(df))
533
668
 
534
- if total_row:
535
- light_blue_rows.append(arr.shape[0] - 1)
669
+ if perc_ids:
670
+ p_ids.extend(perc_ids)
536
671
 
537
- if light_blue_row_ids:
538
- light_blue_rows.extend(light_blue_row_ids)
672
+ if perc_pattern:
673
+ r_id = get_cols_id_with_pattern(df, perc_pattern)
674
+ p_ids.extend(r_id)
675
+ r_align_ids.extend(r_id)
539
676
 
540
- if total_col:
541
- light_blue_cols.append(arr.shape[1] - 1)
677
+ if total_row:
678
+ light_blue_rows.append(arr.shape[0] - 1)
542
679
 
543
- if blue_border:
544
- blue_border_ids.append(arr.shape[0] - 1)
680
+ if light_blue_row_ids:
681
+ light_blue_rows.extend(light_blue_row_ids)
545
682
 
546
- if blue_border_row_ids:
547
- blue_border_ids.extend(blue_border_row_ids)
683
+ if total_col:
684
+ light_blue_cols.append(arr.shape[1] - 1)
548
685
 
549
- if combine_multiindex_cols:
550
- cells_to_merge = get_cells_to_merge(df)
686
+ if blue_border:
687
+ blue_border_ids.append(arr.shape[0] - 1)
551
688
 
552
- ws = wb.create_sheet(sheet_name)
689
+ if blue_border_row_ids:
690
+ blue_border_ids.extend(blue_border_row_ids)
553
691
 
554
- fmt = cell_formatting(
555
- arr=arr,
556
- default_format=STYLES["calibri"],
557
- blue_row_ids=blue_rows,
558
- light_blue_row_ids=light_blue_rows,
559
- light_blue_col_ids=light_blue_cols,
560
- left_align_ids=l_align_ids,
561
- right_align_ids=r_align_ids,
562
- perc_col_ids=p_ids,
563
- perc_col_format=perc_col_format,
564
- number_format=number_format,
565
- blue_border_ids=blue_border_ids,
566
- )
692
+ if combine_multiindex:
693
+ cells_to_merge = get_cells_to_merge(df)
567
694
 
568
- write_worksheet(
569
- ws=ws,
570
- arr=arr,
571
- fmt=fmt,
572
- title=title_tbl,
573
- source=source,
574
- col_filter=col_filter,
575
- autofit_columns=autofit_columns,
576
- cells_to_merge=cells_to_merge,
577
- )
695
+ if autofit_columns == "column_names":
696
+ col_widths = get_max_col_widths(df)
697
+ elif autofit_columns == "all_data":
698
+ col_widths = get_max_col_widths(arr)
699
+ else:
700
+ col_widths = None
701
+
702
+ ws = wb.create_sheet(sheet_name)
703
+
704
+ fmt = cell_formatting(
705
+ arr=arr,
706
+ default_format=STYLES["calibri"],
707
+ blue_row_ids=blue_rows,
708
+ light_blue_row_ids=light_blue_rows,
709
+ light_blue_col_ids=light_blue_cols,
710
+ left_align_ids=l_align_ids,
711
+ right_align_ids=r_align_ids,
712
+ perc_col_ids=p_ids,
713
+ perc_col_format=perc_col_format,
714
+ number_format=number_format,
715
+ blue_border_ids=blue_border_ids,
716
+ )
578
717
 
579
- wb.save(file)
718
+ write_to_worksheet(
719
+ ws=ws,
720
+ arr=arr,
721
+ fmt=fmt,
722
+ title=title_tbl,
723
+ source=title_src,
724
+ col_filter=col_filter,
725
+ col_widths=col_widths,
726
+ cells_to_merge=cells_to_merge,
727
+ min_column_width=min_column_width,
728
+ )
@@ -1,31 +1,31 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: toolsos
3
- Version: 0.1.4
3
+ Version: 0.2.6
4
4
  Summary: OS tools
5
5
  Author-email: OS <d.schmitz@amsterdam.nl>
6
6
  Keywords: tools,Onderzoek & Statistiek
7
7
  Classifier: License :: OSI Approved :: MIT License
8
8
  Classifier: Programming Language :: Python
9
9
  Classifier: Programming Language :: Python :: 3
10
- Requires-Python: >=3.11
10
+ Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
- Provides-Extra: all
13
- Requires-Dist: keyring ; extra == 'all'
14
- Requires-Dist: openpyxl ; extra == 'all'
15
- Requires-Dist: pandas ; extra == 'all'
16
- Requires-Dist: plotly ; extra == 'all'
17
- Requires-Dist: polars ; extra == 'all'
18
- Requires-Dist: pyarrow ; extra == 'all'
19
- Requires-Dist: pyreadstat ; extra == 'all'
20
- Requires-Dist: pyyaml ; extra == 'all'
21
- Requires-Dist: requests ; extra == 'all'
22
- Requires-Dist: sqlalchemy ; extra == 'all'
23
12
  Provides-Extra: dev
24
- Requires-Dist: black ; extra == 'dev'
25
- Requires-Dist: bumpver ; extra == 'dev'
26
- Requires-Dist: isort ; extra == 'dev'
27
- Requires-Dist: pip-tools ; extra == 'dev'
28
- Requires-Dist: pytest ; extra == 'dev'
13
+ Requires-Dist: black; extra == "dev"
14
+ Requires-Dist: bumpver; extra == "dev"
15
+ Requires-Dist: isort; extra == "dev"
16
+ Requires-Dist: pip-tools; extra == "dev"
17
+ Requires-Dist: pytest; extra == "dev"
18
+ Provides-Extra: all
19
+ Requires-Dist: keyring; extra == "all"
20
+ Requires-Dist: openpyxl; extra == "all"
21
+ Requires-Dist: pandas; extra == "all"
22
+ Requires-Dist: plotly; extra == "all"
23
+ Requires-Dist: polars; extra == "all"
24
+ Requires-Dist: pyarrow; extra == "all"
25
+ Requires-Dist: pyreadstat; extra == "all"
26
+ Requires-Dist: pyyaml; extra == "all"
27
+ Requires-Dist: requests; extra == "all"
28
+ Requires-Dist: sqlalchemy; extra == "all"
29
29
 
30
30
  # Tools Onderzoek & Statistiek
31
31
 
@@ -63,5 +63,12 @@ Instructions on building a package can be found [here](https://packaging.python.
63
63
 
64
64
  - make a pypi account
65
65
  - ask to be added as collaborator to toolsos
66
- - first update twine: py -m pip install --upgrade twin
67
- - upload to pypi: twine upload dist/*
66
+ - first update twine: py -m pip install --upgrade twine
67
+ - upload to pypi: twine upload dist/* --skip-existing
68
+
69
+ ## Install to local enviroment for testing
70
+
71
+ - python -m venv local (maak een lokale venv aan)
72
+ - local\Scripts\activate (activeer de venv)
73
+ - pip install -e . (installer toolsos)
74
+ - pip install -r local_requirements.txt (installeer de benodigde dependencies)
@@ -0,0 +1,25 @@
1
+ toolsos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ toolsos/cbs_tools.py,sha256=361cogk0aIU4D4BKHaa7YSOBh64t5C3zrHlqtWx0iIc,3465
3
+ toolsos/create_tables.py,sha256=43FHK3EERjumBtnGhngIdtthZzcc_Qi37lJ1MgATzBg,908
4
+ toolsos/download.py,sha256=88hehmPL5m5d1nrcJjltuh4xrCItF5EYHaZdHOcSt-g,2652
5
+ toolsos/geo.py,sha256=arapy_ol6_so8KZ5gJk9ywXysSz4W8ah-cjrJ3DuxAo,2419
6
+ toolsos/helpers.py,sha256=VeOl-fLgePCbjEmAQdVmYe7z8OE1pISeDDuP1t5QSxM,997
7
+ toolsos/polars_helpers.py,sha256=P3RHLQFeDL7-9U_Q1n4ma_NSkdYAiker4pnc57uluHw,770
8
+ toolsos/database/database_connection.py,sha256=HCP8H_-iE2BOOQDp9v1HOgfUSX45XS-aqw7Nzf8drAU,4359
9
+ toolsos/database/database_transfer.py,sha256=1ghq5VEtKyOdCKdM45uOyrZSoXMuWsdC35R3WNuFvdU,1827
10
+ toolsos/huisstijl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ toolsos/huisstijl/colors.py,sha256=lSCHCdSjge5cGfLfAObd6mV6TaXq3QGImLOmoGJpGkw,1484
12
+ toolsos/huisstijl/graphs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ toolsos/huisstijl/graphs/bargraph.py,sha256=HYl01_euh23iDYSUhnAzYAXS0DhDpg9eLRjJEpeR6iU,2815
14
+ toolsos/huisstijl/graphs/graph_styles.py,sha256=Z9LLH7j8ODTsYMYK0rslacphuiRDcq5_IpSjEEiK2VY,975
15
+ toolsos/huisstijl/graphs/linegraph.py,sha256=dMUarRe31SXaY78OCXLy-PgnU8LlVJ9KkzKaHhDtuuI,698
16
+ toolsos/huisstijl/graphs/piegraph.py,sha256=aEFiEM-9QuhBOjKHSXVuE5bTh-8uucq4FP6O8Vk1vZI,703
17
+ toolsos/huisstijl/graphs/styler.py,sha256=-uZ7pjY1G39XvmaGHQd31gPRxjxmJGhYZk8xhy2JUWc,6623
18
+ toolsos/huisstijl/tables/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ toolsos/huisstijl/tables/table_helpers.py,sha256=jsQ6lw93sxtGJGrUn8X2_LyA2vYYnytngpUI5A_wpWQ,2037
20
+ toolsos/huisstijl/tables/table_styles.py,sha256=oYU6GJcfqlKpZof5PUjPsA7woJ3Tew78CHPyT0_jY6w,1343
21
+ toolsos/huisstijl/tables/tables.py,sha256=2FO-ByLjgs-DbNgem3cDfYJbLbIDzRDqXtjL75WN7kY,24054
22
+ toolsos-0.2.6.dist-info/METADATA,sha256=Fm7Ng8JDYMiDD3Ccm5AYGlGrl3QPZGmRMHC6oNA5o5o,2683
23
+ toolsos-0.2.6.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
24
+ toolsos-0.2.6.dist-info/top_level.txt,sha256=2ClEjUBbtfDQ8oPwvWRy1Sz2nrkLCXlg0mHaMdCWia0,8
25
+ toolsos-0.2.6.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.43.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,24 +0,0 @@
1
- toolsos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- toolsos/cbs_tools.py,sha256=VLhptzy7m5EaET07s6VeAfDz79g1Hs38qeWPyA14wkw,2823
3
- toolsos/create_tables.py,sha256=43FHK3EERjumBtnGhngIdtthZzcc_Qi37lJ1MgATzBg,908
4
- toolsos/database_connection.py,sha256=BlHzLS17KzJP_7R5-IBd8WqgwAt-zDRwJXD4Jx6Tetw,3323
5
- toolsos/database_transfer.py,sha256=1ghq5VEtKyOdCKdM45uOyrZSoXMuWsdC35R3WNuFvdU,1827
6
- toolsos/download.py,sha256=88hehmPL5m5d1nrcJjltuh4xrCItF5EYHaZdHOcSt-g,2652
7
- toolsos/geo.py,sha256=_OexkeUgXcnPW1mw27VN6fMcX2PMUSljLwIg48Xkv3M,2412
8
- toolsos/helpers.py,sha256=VeOl-fLgePCbjEmAQdVmYe7z8OE1pISeDDuP1t5QSxM,997
9
- toolsos/polars_helpers.py,sha256=P3RHLQFeDL7-9U_Q1n4ma_NSkdYAiker4pnc57uluHw,770
10
- toolsos/huisstijl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- toolsos/huisstijl/colors.py,sha256=lSCHCdSjge5cGfLfAObd6mV6TaXq3QGImLOmoGJpGkw,1484
12
- toolsos/huisstijl/graphs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- toolsos/huisstijl/graphs/bargraph.py,sha256=02-2UCnGyvSKXRSOt-f5WuYu8_-6x9tFEkSi-FdD654,2582
14
- toolsos/huisstijl/graphs/graph_styles.py,sha256=fuJUp0ZrZMhaXhHbfBJwOlPLVcCnKU_9MSfw6eddfCI,593
15
- toolsos/huisstijl/graphs/linegraph.py,sha256=eiFNV_bXYM9G-HH0NywPO6WHI9eOZ91kNXoO3aiAvfE,416
16
- toolsos/huisstijl/graphs/piegraph.py,sha256=Pzc3vrbvlkv40V3OYWh6bDDc5arRRwJq7dfRzplPKWY,571
17
- toolsos/huisstijl/graphs/styler.py,sha256=0Mm37y_bkSy8mRtNzUgYfW_vrYZrV6_h1GLqQ8y8bXI,3434
18
- toolsos/huisstijl/tables/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
- toolsos/huisstijl/tables/table_styles.py,sha256=oYU6GJcfqlKpZof5PUjPsA7woJ3Tew78CHPyT0_jY6w,1343
20
- toolsos/huisstijl/tables/tables.py,sha256=1x4htBUrX8FUWSmPiOI7nCNm9R4NYv-J-cK33lOXKpM,19462
21
- toolsos-0.1.4.dist-info/METADATA,sha256=f5Vf3pkNPx8k2rJSziNvA5wNT7neRghAb1-xFe6wYsA,2417
22
- toolsos-0.1.4.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
23
- toolsos-0.1.4.dist-info/top_level.txt,sha256=2ClEjUBbtfDQ8oPwvWRy1Sz2nrkLCXlg0mHaMdCWia0,8
24
- toolsos-0.1.4.dist-info/RECORD,,