stage-foundary 0.1.4__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.
@@ -0,0 +1,9 @@
1
+ """
2
+ stage_foundary
3
+
4
+ Collection of common stages used to create all kind of data analyis
5
+ """
6
+
7
+ __version__ = "0.1.0"
8
+ __author__ = "Reiner Jung"
9
+ __credits__ = "Zentrale IT des Landes Schleswig-Holstein"
@@ -0,0 +1,247 @@
1
+ import csv
2
+ import datetime
3
+ import logging
4
+ import sys
5
+
6
+ from stage_foundary.teetime import ConsumerStage, ProducerStage, SinkStage
7
+
8
+ ##########################################
9
+ # Generic functions
10
+ ##########################################
11
+
12
+
13
+ class CsvException(Exception):
14
+ pass
15
+
16
+
17
+ #
18
+ # Abstract csv record processing
19
+ class AbstractCsvRecordProcessing:
20
+ def __init__(
21
+ self,
22
+ logger: logging.Logger,
23
+ delimiter: str,
24
+ skip_header: int,
25
+ use_header: bool,
26
+ encoding: str = "utf-8",
27
+ ):
28
+ self.logger = logger
29
+ self.skip_header = skip_header
30
+ self.delimiter = delimiter
31
+ self.use_header = use_header
32
+ self.encoding = encoding
33
+ self.columns: dict[str, list[int]] = {}
34
+
35
+ def read_file(self, filename: str):
36
+ self.logger.info(f"reading {filename}")
37
+ self.current_filename = filename
38
+ with open(filename, newline="", encoding=self.encoding) as csv_file:
39
+ try:
40
+ reader = csv.reader(csv_file, delimiter=self.delimiter, quotechar='"')
41
+
42
+ self.columns = {}
43
+ self.process_start(filename)
44
+ for i, row in enumerate(reader):
45
+ if self.skip_header > i:
46
+ if i == 0 and self.use_header:
47
+ for j, column_name in enumerate(row):
48
+ column = self.columns.get(column_name)
49
+ if column is None:
50
+ self.columns[column_name] = [j]
51
+ else:
52
+ self.columns[column_name].append(j)
53
+ else:
54
+ self.process_row(row)
55
+
56
+ self.process_complete(filename)
57
+ except UnicodeDecodeError as e:
58
+ self.logger.error(
59
+ f"The file '{filename}' contains an UTF error in [{i}] {e}"
60
+ )
61
+ except FileNotFoundError as e:
62
+ self.logger.error(f"The file '{filename}' does not exist. {e}")
63
+ sys.exit(-1)
64
+
65
+ def get_value(self, name: str, row: list, optional=None) -> list:
66
+ columns: list[int] | None = self.columns.get(name)
67
+ if columns is None:
68
+ if optional is None:
69
+ self.logger.error(
70
+ f"Column name {name} not found in CSV file {self.current_filename}"
71
+ )
72
+ raise CsvException(
73
+ f"Column name {name} not found in CSV file {self.current_filename}"
74
+ )
75
+ else:
76
+ return [optional]
77
+ result = []
78
+ for column in columns:
79
+ if len(row) > column:
80
+ result.append(row[column])
81
+ return result
82
+
83
+ def get_scalar_value(self, name: str, row: list, optional=None) -> any:
84
+ result = self.get_value(name, row, optional)
85
+ if len(result) == 1:
86
+ return result[0]
87
+ elif len(result) > 1:
88
+ raise CsvException("scalar expected, but list found.")
89
+ else:
90
+ return None
91
+
92
+ def get_date_value(self, name: str, row):
93
+ date_string = self.get_scalar_value(name, row)
94
+ if date_string is None or len(date_string) == 0:
95
+ return None
96
+ else:
97
+ return datetime.datetime.strptime(date_string, "%d.%m.%Y %H:%M")
98
+
99
+ def get_int_value(self, name: str, row: list, optional=None) -> int:
100
+ try:
101
+ return self.convert_int(self.get_scalar_value(name, row, optional))
102
+ except CsvException as e:
103
+ if optional is not None:
104
+ self.logger.warning(f"Error for attribute {name}: {e}")
105
+ return optional
106
+ else:
107
+ self.logger.error(f"Error for attribute {name}: {e}")
108
+ raise CsvException("Error for attribute {name}: {e}")
109
+ except ValueError as e:
110
+ if optional is not None:
111
+ self.logger.warning(f"Error for attribute {name}: {e}")
112
+ return optional
113
+ else:
114
+ self.logger.error(f"Error for attribute {name}: {e}")
115
+ raise CsvException("Error for attribute {name}: {e}")
116
+
117
+ def get_float_value(self, name: str, row: list, optional=None) -> float:
118
+ try:
119
+ return self.convert_float(self.get_scalar_value(name, row, optional))
120
+ except CsvException as e:
121
+ if optional is not None:
122
+ self.logger.warning(f"Error for attribute {name}: {e}")
123
+ return optional
124
+ else:
125
+ self.logger.error(f"Error for attribute {name}: {e}")
126
+ raise CsvException("Error for attribute {name}: {e}")
127
+ except ValueError as e:
128
+ if optional is not None:
129
+ self.logger.warning(f"Error for attribute {name}: {e}")
130
+ return optional
131
+ else:
132
+ self.logger.error(f"Error for attribute {name}: {e}")
133
+ raise CsvException("Error for attribute {name}: {e}")
134
+
135
+ def process_row(self, row: list) -> None:
136
+ pass
137
+
138
+ def process_start(self, filename: str):
139
+ pass
140
+
141
+ def process_complete(self, filename: str):
142
+ pass
143
+
144
+ def convert_int(self, value: str) -> int:
145
+ if value is not None and isinstance(value, str):
146
+ if value.strip() != "":
147
+ return int(value)
148
+ else:
149
+ raise CsvException("Empty value is not an integer number")
150
+ return 0
151
+
152
+ def convert_float(self, value: str) -> float:
153
+ if value is not None and isinstance(value, str):
154
+ if value.strip() != "":
155
+ return float(value)
156
+ else:
157
+ raise CsvException("Empty value is not an floating point number")
158
+ return 0.0
159
+
160
+
161
+ #
162
+ # Abstract record producer for CSV files
163
+ #
164
+ class AbstractCsvRecordProducer(ProducerStage, AbstractCsvRecordProcessing):
165
+ # @param skip_header = skip number of lines from the header
166
+ def __init__(
167
+ self,
168
+ logger: logging.Logger,
169
+ filenames,
170
+ delimiter: str = ";",
171
+ skip_header: int = 1,
172
+ use_header: bool = False,
173
+ encoding: str = "utf-8",
174
+ ):
175
+ ProducerStage.__init__(self, logger)
176
+ AbstractCsvRecordProcessing.__init__(
177
+ self,
178
+ logger=logger,
179
+ delimiter=delimiter,
180
+ skip_header=skip_header,
181
+ use_header=use_header,
182
+ encoding=encoding,
183
+ )
184
+ self.filenames = filenames
185
+ self.columns: dict[str, list[int]] = {}
186
+
187
+ def execute(self):
188
+ if isinstance(self.filenames, str):
189
+ self.read_file(filename=self.filenames)
190
+ else:
191
+ for filename in self.filenames:
192
+ self.read_file(filename=filename)
193
+ self.work_complete()
194
+
195
+
196
+ #
197
+ # Abstract record reader for CSV files
198
+ #
199
+ class AbstractCsvRecordReader(ConsumerStage, AbstractCsvRecordProcessing):
200
+ # @param skip_header = skip number of lines from the header
201
+ def __init__(
202
+ self,
203
+ logger: logging.Logger,
204
+ delimiter: str = ";",
205
+ skip_header: int = 1,
206
+ use_header: bool = False,
207
+ encoding: str = "utf-8",
208
+ ):
209
+ ConsumerStage.__init__(self, logger)
210
+ AbstractCsvRecordProcessing.__init__(
211
+ self,
212
+ logger=logger,
213
+ delimiter=delimiter,
214
+ skip_header=skip_header,
215
+ use_header=use_header,
216
+ encoding=encoding,
217
+ )
218
+
219
+ def consume(self, filename: str):
220
+ self.columns: dict[str, list[int]] = {}
221
+ self.read_file(filename=filename)
222
+
223
+
224
+ ############################################
225
+ # Writer
226
+ ############################################
227
+
228
+
229
+ class AbstractCsvRecordSink(SinkStage):
230
+ def __init__(
231
+ self, logger: logging.Logger, filename: str, delimiter: str, fieldnames: list
232
+ ):
233
+ super().__init__(logger)
234
+ with open(filename, "w", newline="") as self.output_file:
235
+ self.csv_writer = csv.writer(
236
+ self.output_file,
237
+ delimiter=delimiter,
238
+ quotechar='"',
239
+ quoting=csv.QUOTE_ALL,
240
+ )
241
+ self.csv_writer.writerow(fieldnames)
242
+
243
+ def consume(self, entry):
244
+ pass
245
+
246
+ def on_terminate(self):
247
+ self.output_file.close()
@@ -0,0 +1,146 @@
1
+ """
2
+ Created on Mon Mar 30 08:32:13 2026
3
+
4
+ @author: Reiner Jung
5
+ """
6
+
7
+ from dataclasses import dataclass
8
+ from enum import Enum
9
+
10
+
11
+ class EHorizontalOrientation(Enum):
12
+ LEFT = 1
13
+ CENTER = 2
14
+ RIGHT = 3
15
+
16
+
17
+ class EVerticalOrientation(Enum):
18
+ TOP = 1
19
+ CENTER = 2
20
+ BOTTOM = 3
21
+
22
+
23
+ @dataclass
24
+ class AbstractPageElement:
25
+ def print(self, indent):
26
+ pass
27
+
28
+
29
+ @dataclass
30
+ class Table(AbstractPageElement):
31
+ header: list # [str]
32
+ orientations: list # [EHorizontalOrientation]
33
+ widths: list # [str]
34
+ rows: list # [list[AbstractPageElement]]
35
+
36
+
37
+ @dataclass
38
+ class Container(AbstractPageElement):
39
+ children: list
40
+
41
+ def print(self, indent):
42
+ for item in self.children:
43
+ item.print(f" {indent}")
44
+
45
+
46
+ @dataclass
47
+ class Page(Container):
48
+ pass
49
+
50
+
51
+ @dataclass
52
+ class PlainText(AbstractPageElement):
53
+ text: str
54
+
55
+
56
+ @dataclass
57
+ class Link(Container):
58
+ link: str
59
+ format: str
60
+
61
+
62
+ @dataclass
63
+ class Headline(Container):
64
+ level: int
65
+
66
+ def print(self, indent):
67
+ print(f"{indent}Headline {self.level}")
68
+ super().print(indent)
69
+
70
+
71
+ @dataclass
72
+ class NoFormat(Container):
73
+ def print(self, indent):
74
+ print(f"{indent}No Format")
75
+ super().print(indent)
76
+
77
+
78
+ @dataclass
79
+ class CodeLine(PlainText):
80
+ def print(self, indent):
81
+ print(f"{indent}CODE {self.text}")
82
+
83
+
84
+ @dataclass
85
+ class Code(Container):
86
+ def print(self, indent):
87
+ print(f"{indent}Code")
88
+ super().print(indent)
89
+
90
+
91
+ @dataclass
92
+ class Paragraph(Container):
93
+ def print(self, indent):
94
+ print(f"{indent}Paragraph")
95
+ super().print(indent)
96
+
97
+
98
+ @dataclass
99
+ class Line(Container):
100
+ pass
101
+
102
+
103
+ @dataclass
104
+ class Span(Container):
105
+ format: str
106
+
107
+ def print(self, indent):
108
+ print(f"{indent}Span {self.format}")
109
+ super().print(indent + " ")
110
+
111
+
112
+ @dataclass
113
+ class Item(Container):
114
+ level: int
115
+
116
+ def print(self, indent):
117
+ print(f"{indent}LI {self.level}")
118
+ super().print(f"{indent} ")
119
+
120
+
121
+ @dataclass
122
+ class Enumeration(Container):
123
+ level: int
124
+
125
+ def print(self, indent):
126
+ print(f"{indent}LI {self.level}")
127
+ super().print(f"{indent} ")
128
+
129
+
130
+ @dataclass
131
+ class ItemList(Container):
132
+ def print(self, indent):
133
+ print(f"{indent}List")
134
+ super().print(indent)
135
+
136
+
137
+ @dataclass
138
+ class EnumerationList(Container):
139
+ def print(self, indent):
140
+ print(f"{indent}Enumeration")
141
+ super().print(indent)
142
+
143
+
144
+ @dataclass
145
+ class Newline(AbstractPageElement):
146
+ pass