pyfwf 1.0.0__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.
pyfwf/__init__.py ADDED
@@ -0,0 +1,25 @@
1
+ """
2
+ The MIT License (MIT)
3
+
4
+ Copyright 2015 Umbrella Tech.
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
7
+ this software and associated documentation files (the "Software"), to deal in
8
+ the Software without restriction, including without limitation the rights to
9
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10
+ the Software, and to permit persons to whom the Software is furnished to do so,
11
+ subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ """
23
+
24
+
25
+ __author__ = 'Kelson da Costa Medeiros <kelsoncm@gmail.com>'
pyfwf/columns.py ADDED
@@ -0,0 +1,216 @@
1
+ """
2
+ The MIT License (MIT)
3
+
4
+ Copyright 2015 Umbrella Tech.
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
7
+ this software and associated documentation files (the "Software"), to deal in
8
+ the Software without restriction, including without limitation the rights to
9
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10
+ the Software, and to permit persons to whom the Software is furnished to do so,
11
+ subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ """
23
+
24
+
25
+ __author__ = 'Kelson da Costa Medeiros <kelsoncm@gmail.com>'
26
+
27
+
28
+ import re
29
+ from datetime import datetime, date, time
30
+ from .hydrating import Hydrator
31
+
32
+
33
+ class AbstractColumn(Hydrator):
34
+ to_str_assertion_types = None
35
+ to_str_assertion_class = None
36
+ to_str_none_pad = None
37
+ to_str_pad_template = None
38
+ hydrating_args = ['name', 'size', 'description']
39
+
40
+ def __init__(self, _name: str, size: int, description: str=None):
41
+ super(AbstractColumn, self).__init__()
42
+ assert isinstance(_name, str), 'O campo name deve ser uma string'
43
+ assert _name and _name.rstrip(), 'O campo column_name deve ser uma string válida e não branca'
44
+ assert isinstance(size, int), 'O campo size deve ser um inteiro'
45
+ assert size > 0, 'O campo size deve ser maior que 0'
46
+ if description is None:
47
+ description = _name
48
+ else:
49
+ assert isinstance(description, str), 'O campo description deve ser uma string'
50
+
51
+ self.name = _name
52
+ self.size = size
53
+ self.description = description
54
+ self.start = None
55
+
56
+ @property
57
+ def end(self):
58
+ assert isinstance(self.start, int), 'O campo start deve ser um inteiro'
59
+ assert self.start > 0, 'O campo start deve ser maior que 0'
60
+ return self.start + self.size - 1
61
+
62
+ def to_value(self, slice):
63
+ assert isinstance(slice, str), 'Informe uma string para converter corretamente'
64
+ assert len(slice) == self.size, "A string deve ter exatamente o tamanho do campo '%s' (%s)" % \
65
+ (self.name, self.size)
66
+ return slice
67
+
68
+ def _validate_to_str_size(self, value):
69
+ assert len(value) == self.size, "O valor a ser serializado para o campo '%s' não pode ser diferente de %d " \
70
+ % (self.name, self.size)
71
+ return value
72
+
73
+ def to_str_assertion(self, value):
74
+ return isinstance(value, self.to_str_assertion_class)
75
+
76
+ def to_str(self, value: str):
77
+ assert value is None or self.to_str_assertion(value), \
78
+ "O campo '%s' só aceita '%s' or 'None'" % (self.name, self.to_str_assertion_types)
79
+
80
+ if value is None:
81
+ return self.to_str_none_pad * self.size
82
+ else:
83
+ return self._validate_to_str_size((self.to_str_pad_template % self.size).format(value))
84
+
85
+
86
+ class CharColumn(AbstractColumn):
87
+
88
+ to_str_assertion_types = 'str'
89
+ to_str_assertion_class = str
90
+ to_str_none_pad = ' '
91
+ to_str_pad_template = '{0: <%d}'
92
+
93
+ def to_value(self, slice: str):
94
+ return super(CharColumn, self).to_value(slice).strip()
95
+
96
+
97
+ class RightCharColumn(CharColumn):
98
+ to_str_pad_template = "{0: >%d}"
99
+
100
+
101
+ class PositiveIntegerColumn(AbstractColumn):
102
+ to_str_assertion_types = 'positive int'
103
+ to_str_assertion_class = int
104
+ to_str_none_pad = '0'
105
+ to_str_pad_template = "{0:0%dd}"
106
+
107
+ def to_str_assertion(self, value):
108
+ return isinstance(value, int) and not isinstance(value, bool) and value >= 0
109
+
110
+ def to_value(self, slice: str):
111
+ _value = int(super(PositiveIntegerColumn, self).to_value(slice))
112
+ assert _value >= 0, \
113
+ "Informe uma string para converter corretamente, '%s' não é um '%s'" % (slice, self.to_str_assertion_types)
114
+ return _value
115
+
116
+
117
+ class PositiveDecimalColumn(PositiveIntegerColumn):
118
+ to_str_assertion_types = 'positive decimal'
119
+ to_str_assertion_class = float
120
+ hydrating_args = ['name', 'size', 'decimals', 'description']
121
+
122
+ def __init__(self, _name: str, size: int, decimals: int=2, description: str=None):
123
+ super(PositiveDecimalColumn, self).__init__(_name, size, description)
124
+ assert isinstance(decimals, int), 'Os decimais devem ser um inteiro'
125
+ assert decimals > 0, 'Os decimais devem ser maior que 0'
126
+ assert size > decimals, 'Os decimais devem ser menores que o size'
127
+ self.decimals = decimals
128
+
129
+ def to_value(self, slice: str):
130
+ return super(PositiveDecimalColumn, self).to_value(slice) / pow(10, self.decimals)
131
+
132
+ def to_str_assertion(self, value):
133
+ return isinstance(value, float) and not isinstance(value, bool) and value >= 0.0
134
+
135
+ def to_str(self, value: str):
136
+ assert value is None or self.to_str_assertion(value), \
137
+ "O campo '%s' só aceita '%s' or 'None'" % (self.name, self.to_str_assertion_types)
138
+
139
+ if value is None:
140
+ return self.to_str_none_pad * self.size
141
+ else:
142
+ _value = int(value * pow(10, self.decimals))
143
+ return self._validate_to_str_size((self.to_str_pad_template % self.size).format(_value))
144
+
145
+
146
+ class DateTimeColumn(AbstractColumn):
147
+ to_str_assertion_types = 'datetime'
148
+ to_str_assertion_class = datetime
149
+ to_str_none_pad = '0'
150
+ format_num_elements = 5
151
+ hydrating_args = ['name', 'format', 'description']
152
+
153
+ def __init__(self, _name: str, _format: str='%d%m%Y%H%M', description: str=None):
154
+ assert isinstance(_name, str), \
155
+ 'O campo name deve ser uma string'
156
+ assert _name and _name.strip(), \
157
+ 'O campo column_name deve ser uma string válida e não branca'
158
+ assert isinstance(_format, str), \
159
+ "O argumento '_format' do campo '%s' deve ser uma string" % _name
160
+ assert _format and _format.strip(), \
161
+ "O argumento '_format' do campo '%s' deve ser uma string válida e não branca" % _name
162
+ assert len([x for x in re.finditer(re.compile('(%[a-z,A-Z])'), _format)]) == self.format_num_elements, \
163
+ "O argumento '_format' (%s) do campo '%s' deve ter um formato de data/hora válido" % (_format, _name)
164
+
165
+ _size = len(datetime(2001, 12, 31, 13, 59).strftime(_format))
166
+
167
+ self.to_str_pad_templating = ''
168
+ super(DateTimeColumn, self).__init__(_name, _size, description)
169
+
170
+ self.format = _format
171
+
172
+ def to_value(self, slice: str):
173
+ _value = super(DateTimeColumn, self).to_value(slice)
174
+ try:
175
+ if _value == self.to_str_none_pad * self.size:
176
+ return None
177
+ else:
178
+ return datetime.strptime(_value, self.format)
179
+ except ValueError:
180
+ raise ValueError("O valor '%s' do campo '%s' é inválido para o formato '%s'" %
181
+ (_value, self.name, self.format))
182
+
183
+ def to_str(self, value: datetime):
184
+ assert value is None or self.to_str_assertion(value), \
185
+ "O campo '%s' só aceita '%s' or 'None'" % (self.name, self.to_str_assertion_types)
186
+
187
+ if value is None:
188
+ return self.to_str_none_pad * self.size
189
+ else:
190
+ return self._validate_to_str_size(value.strftime(self.format))
191
+
192
+
193
+ class DateColumn(DateTimeColumn):
194
+ to_str_assertion_types = 'date'
195
+ to_str_assertion_class = date
196
+ format_num_elements = 3
197
+
198
+ def __init__(self, _name: str, _format: str='%d%m%Y', description: str=None):
199
+ super(DateColumn, self).__init__(_name, _format, description)
200
+
201
+ def to_value(self, slice: str):
202
+ result = super(DateColumn, self).to_value(slice)
203
+ return result.date() if result is not None else None
204
+
205
+
206
+ class TimeColumn(DateTimeColumn):
207
+ to_str_assertion_types = 'time'
208
+ to_str_assertion_class = time
209
+ format_num_elements = 2
210
+
211
+ def __init__(self, _name: str, _format: str='%H%M', description: str=None):
212
+ super(TimeColumn, self).__init__(_name, _format, description)
213
+
214
+ def to_value(self, slice: str):
215
+ result = super(TimeColumn, self).to_value(slice)
216
+ return result.time() if result is not None else None
pyfwf/descriptors.py ADDED
@@ -0,0 +1,111 @@
1
+ """
2
+ The MIT License (MIT)
3
+
4
+ Copyright 2015 Umbrella Tech.
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
7
+ this software and associated documentation files (the "Software"), to deal in
8
+ the Software without restriction, including without limitation the rights to
9
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10
+ the Software, and to permit persons to whom the Software is furnished to do so,
11
+ subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ """
23
+
24
+
25
+ __author__ = 'Kelson da Costa Medeiros <kelsoncm@gmail.com>'
26
+
27
+
28
+ from io import StringIO
29
+ import importlib
30
+ from typing import List, Dict
31
+ from .columns import AbstractColumn
32
+ from .hydrating import Hydrator
33
+
34
+
35
+ class RowDescriptor(Hydrator):
36
+
37
+ def __init__(self, columns: List[AbstractColumn]):
38
+ super(RowDescriptor, self).__init__()
39
+ assert isinstance(columns, list), 'columns deve ser uma List'
40
+ assert columns != [], 'columns deve ter ao menos 1 elemento'
41
+ self.columns = columns
42
+ last = None
43
+ for column in columns:
44
+ column.start = last.end + 1 if last is not None else 1
45
+ last = column
46
+ self.validate_positions()
47
+
48
+ @property
49
+ def line_size(self):
50
+ return self.columns[len(self.columns)-1].end
51
+
52
+ def validate_positions(self):
53
+ prev = None
54
+ for col in self.columns:
55
+ if prev is None:
56
+ assert col.start == 1, 'A coluna %s deve começar com 1' % col.name
57
+ else:
58
+ assert prev.end + 1 == col.start, 'A coluna %s (starts in %d) deve começar imediatamente após ' \
59
+ 'a coluna %s (ends in %d)' % \
60
+ (col.name, col.start, prev.name, prev.end)
61
+ prev = col
62
+
63
+ def get_values(self, row):
64
+ return {col.name: col.to_value(row[col.start-1:col.end]) for col in self.columns}
65
+
66
+
67
+ class HeaderRowDescriptor(RowDescriptor):
68
+ pass
69
+
70
+
71
+ class FooterRowDescriptor(RowDescriptor):
72
+ pass
73
+
74
+
75
+ class DetailRowDescriptor(RowDescriptor):
76
+ pass
77
+
78
+
79
+ class FileDescriptor(Hydrator):
80
+ def __init__(self,
81
+ details: List[DetailRowDescriptor],
82
+ header: HeaderRowDescriptor=None,
83
+ footer: FooterRowDescriptor=None):
84
+ super(FileDescriptor, self).__init__()
85
+
86
+ assert isinstance(details, list), \
87
+ 'details deve ser uma List'
88
+ assert len(details) > 0, \
89
+ 'details deve ser uma List com ao menos 1 DetailRowDescriptor'
90
+ for detail in details:
91
+ assert isinstance(detail, DetailRowDescriptor), \
92
+ 'details deve ser uma List de DetailRowDescriptor'
93
+ assert isinstance(header, HeaderRowDescriptor) or header is None, \
94
+ 'header deve ser um HeaderRowDescriptor'
95
+ assert isinstance(footer, FooterRowDescriptor) or footer is None, \
96
+ 'footer_descriptor deve ser um FooterRowDescriptor'
97
+
98
+ self.header = header
99
+ self.footer = footer
100
+ self.details = details
101
+ self.validate_sizes()
102
+ self.line_size = self.details[0].line_size
103
+
104
+ def validate_sizes(self):
105
+ h = self.header.line_size if self.header else 0
106
+ f = self.footer.line_size if self.footer else 0
107
+ d = self.details[0].line_size
108
+ ln = [x.line_size for x in self.details]
109
+ s = sum(ln)
110
+ assert (s == d * len(self.details)) and (d == h or h == 0) and (d == f or f == 0), \
111
+ 'O tamanho das linhas header (%d), footer (%d) e das details (%s) devem ser iguais' % (h, f, ln)
pyfwf/hydrating.py ADDED
@@ -0,0 +1,100 @@
1
+ """
2
+ The MIT License (MIT)
3
+
4
+ Copyright 2015 Umbrella Tech.
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
7
+ this software and associated documentation files (the "Software"), to deal in
8
+ the Software without restriction, including without limitation the rights to
9
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10
+ the Software, and to permit persons to whom the Software is furnished to do so,
11
+ subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ """
23
+
24
+
25
+ __author__ = 'Kelson da Costa Medeiros <kelsoncm@gmail.com>'
26
+
27
+ __all__ = ['hydrate_object', 'dehydrate_object', 'Hydrator']
28
+
29
+ import importlib
30
+ from typing import Dict, List
31
+
32
+
33
+ def get_full_class_name(instance_or_class):
34
+ if isinstance(instance_or_class, type):
35
+ return instance_or_class.__module__ + "." + instance_or_class.__qualname__
36
+ else:
37
+ return instance_or_class.__module__ + "." + instance_or_class.__class__.__qualname__
38
+
39
+
40
+ def create_class(full_class_name, *args, **kwargs):
41
+ module_name, class_name = full_class_name.rsplit(".", 1)
42
+ MyClass = getattr(importlib.import_module(module_name), class_name)
43
+ return MyClass(*args, **kwargs)
44
+
45
+
46
+ def assert_element_isinstance(name, lst, cls):
47
+ if name in lst:
48
+ assert isinstance(lst[name], cls), '%s has informed, but is not a %s' % (name, cls)
49
+
50
+
51
+ def assert_isinstance(name, value, cls):
52
+ assert isinstance(value, cls), '%s is not a %s' % (name, cls)
53
+
54
+
55
+ def hydrate_object(representation: Dict):
56
+ assert_isinstance('representation', representation, Dict)
57
+ assert '_hydrate_as' in representation, '_hydrate_as is required'
58
+ assert_element_isinstance('_hydrate_as', representation, str)
59
+ assert_element_isinstance('args', representation, List)
60
+ assert_element_isinstance('kwargs', representation, Dict)
61
+ assert_element_isinstance('attributes', representation, Dict)
62
+
63
+ def hydrate_if_hydratable(o):
64
+ return hydrate_object(o) if isinstance(o, Dict) and '_hydrate_as' in o else o
65
+
66
+ hydrate_as = representation['_hydrate_as']
67
+ args = [hydrate_if_hydratable(arg)
68
+ for arg in (representation['args'] if 'args' in representation else [])]
69
+ kwargs = {k: hydrate_if_hydratable(v)
70
+ for k, v in (representation['kwargs'] if 'kwargs' in representation else {}).items()}
71
+ attributes = representation['attributes'] if 'attributes' in representation else {}
72
+
73
+ instance = create_class(hydrate_as, *args, **kwargs)
74
+ for attr_name in attributes.keys():
75
+ setattr(instance, attr_name, attributes[attr_name])
76
+ return instance
77
+
78
+
79
+ def dehydrate_object(obj):
80
+ def dehydrate_if_hydratable(o):
81
+ return dehydrate_object(o) if isinstance(o, Hydrator) else o
82
+
83
+ result = {"_hydrate_as": get_full_class_name(obj)}
84
+ if hasattr(obj, 'hydrating_args'):
85
+ result["args"] = [dehydrate_if_hydratable(getattr(obj, name)) for name in obj.hydrating_args]
86
+ if hasattr(obj, 'hydrating_kwargs'):
87
+ result["kwargs"] = {name: dehydrate_if_hydratable(getattr(obj, name)) for name in obj.hydrating_kwargs}
88
+ if hasattr(obj, 'hydrating_attributes'):
89
+ result["attributes"] = {name: dehydrate_if_hydratable(getattr(obj, name)) for name in obj.hydrating_attributes}
90
+ return result
91
+
92
+
93
+ class Hydrator(object):
94
+
95
+ def dehydrate(self):
96
+ return dehydrate_object(self)
97
+
98
+ @classmethod
99
+ def hydrate(cls, representation: Dict):
100
+ return hydrate_object(representation)
pyfwf/readers.py ADDED
@@ -0,0 +1,86 @@
1
+ """
2
+ The MIT License (MIT)
3
+
4
+ Copyright 2015 Umbrella Tech.
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
7
+ this software and associated documentation files (the "Software"), to deal in
8
+ the Software without restriction, including without limitation the rights to
9
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10
+ the Software, and to permit persons to whom the Software is furnished to do so,
11
+ subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ """
23
+
24
+
25
+ __author__ = 'Kelson da Costa Medeiros <kelsoncm@gmail.com>'
26
+
27
+
28
+ from typing import Iterable, List
29
+ from io import StringIO, TextIOWrapper
30
+ from .descriptors import FileDescriptor
31
+ from collections import abc
32
+
33
+
34
+ __all__ = ['Reader']
35
+
36
+
37
+ class Reader:
38
+
39
+ def __init__(self, _iterable: Iterable[str], file_descriptor: FileDescriptor, newline: str="\n\r"):
40
+ assert isinstance(_iterable, abc.Iterable), \
41
+ 'O argumento _iterable tem que ser um Iterator'
42
+ assert isinstance(file_descriptor, FileDescriptor), \
43
+ 'O argumento file_descriptor tem que ser um FileDescriptor'
44
+ assert isinstance(newline, str) and newline in ["\n", "\r", "\n\r"], \
45
+ 'O argumento newline tem que ser uma str e conter "\\n", "\\r" ou "\\n\\r"'
46
+
47
+ self.iterable = _iterable
48
+ self.file_descriptor = file_descriptor
49
+ self.line_num = 0
50
+ self.newline = newline
51
+ self.lines_count = 0
52
+
53
+ if isinstance(self.iterable, StringIO):
54
+ self.filesize = len(self.iterable.getvalue())
55
+ elif isinstance(self.iterable, TextIOWrapper):
56
+ self.filesize = len(self.iterable.read())
57
+ elif isinstance(self.iterable, str):
58
+ self.filesize = len(self.iterable)
59
+ self.iterable = StringIO(self.iterable)
60
+ elif isinstance(self.iterable, List):
61
+ self.iterable = iter(_iterable)
62
+ self.lines_count = len(_iterable)
63
+ self.filesize = sum([len(r) for r in _iterable])
64
+ else:
65
+ raise TypeError('Unsupported Iterable')
66
+
67
+ assert float(self.filesize) % float(self.file_descriptor.line_size + len(self.newline)) == 0, \
68
+ "Algumas linha não tem o tamanho correto (%d) ou não tem a quebra de linha adequada (%s), " \
69
+ "total de bytes %d e total de linhas %f" % \
70
+ (self.file_descriptor.line_size, self.newline.replace("\n", "\\n").replace("\r", "\\r"),
71
+ self.filesize, float(self.filesize) / float(self.file_descriptor.line_size + len(self.newline)))
72
+ self.lines_count = self.filesize / (self.file_descriptor.line_size + len(self.newline))
73
+
74
+ def __iter__(self):
75
+ return self
76
+
77
+ def __next__(self):
78
+ row = next(self.iterable)
79
+ self.line_num += 1
80
+ lc = row[:-len(self.newline)]
81
+ if self.file_descriptor.header and self.line_num == 1:
82
+ return self.file_descriptor.header.get_values(lc)
83
+ elif self.file_descriptor.footer and self.lines_count and self.lines_count == self.line_num:
84
+ return self.file_descriptor.footer.get_values(lc)
85
+ else:
86
+ return self.file_descriptor.details[0].get_values(lc)
pyfwf/renders.py ADDED
@@ -0,0 +1,69 @@
1
+ """
2
+ The MIT License (MIT)
3
+
4
+ Copyright 2015 Umbrella Tech.
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
7
+ this software and associated documentation files (the "Software"), to deal in
8
+ the Software without restriction, including without limitation the rights to
9
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10
+ the Software, and to permit persons to whom the Software is furnished to do so,
11
+ subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ """
23
+
24
+
25
+ __author__ = 'Kelson da Costa Medeiros <kelsoncm@gmail.com>'
26
+
27
+
28
+ from io import StringIO
29
+ import importlib
30
+ from typing import List, Dict
31
+ from .columns import AbstractColumn
32
+ from .descriptors import FileDescriptor
33
+
34
+
35
+ def render_as_markdown(file_descriptor: FileDescriptor, out: StringIO):
36
+ def table_header(title, max_colname_size, max_coltype_size):
37
+ out.write(("# {title}\n\n"
38
+ "| # | {name:<%d} | Size | Start | End | {type:<%d} | Description\n"
39
+ "| ---- | {sep:-<%d} | ---- | ----- | ---- | {sep:-<%d} | -----------\n" %
40
+ (max_colname_size, max_coltype_size, max_colname_size, max_coltype_size)).
41
+ format(title=title, name='Column', type='Type', sep='-'))
42
+
43
+ def table_body(cols, max_colname_size, max_coltype_size):
44
+ line = 0
45
+ template = "| {0:>4d} | {1: <%d} | {2:>4d} | {3:>5d} | {4:>4d} | {5:<%d} | {6}\n" \
46
+ % (max_colname_size, max_coltype_size)
47
+ for col in cols:
48
+ line += 1
49
+ out.write(template.format(line, col.name, col.size, col.start, col.end, col.__class__.__name__,
50
+ col.description))
51
+
52
+ def table(title, cols, trailling=True):
53
+ max_colname_size = max([len(col.name) for col in cols])
54
+ max_coltype_size = max([len(col.__class__.__name__) for col in cols])
55
+ table_header(title, max_colname_size, max_coltype_size)
56
+ table_body(cols, max_colname_size, max_coltype_size)
57
+ if trailling:
58
+ out.write("\n\n")
59
+
60
+ if file_descriptor.header:
61
+ table("HEADER", file_descriptor.header.columns)
62
+
63
+ detail_num = 1
64
+ for detail in file_descriptor.details:
65
+ table("DETAILS %s" % detail_num, detail.columns)
66
+ detail_num += 1
67
+
68
+ if file_descriptor.footer:
69
+ table("FOOTER", file_descriptor.footer.columns, False)
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyfwf
3
+ Version: 1.0.0
4
+ Summary: Python library to manipulate fixed width file (FWF)
5
+ Author: kelsoncm
6
+ License: The MIT License (MIT)
7
+
8
+ Copyright (c) 2016 kelsoncm
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+
29
+ Classifier: Development Status :: 5 - Production/Stable
30
+ Classifier: Intended Audience :: Developers
31
+ Classifier: License :: OSI Approved :: MIT License
32
+ Classifier: Programming Language :: Python :: 3
33
+ Classifier: Programming Language :: Python :: 3.10
34
+ Classifier: Programming Language :: Python :: 3.11
35
+ Classifier: Programming Language :: Python :: 3.12
36
+ Classifier: Programming Language :: Python :: 3.13
37
+ Classifier: Programming Language :: Python :: 3.14
38
+ Requires-Python: >=3.10
39
+ Description-Content-Type: text/markdown
40
+ License-File: LICENSE.md
41
+ Provides-Extra: dev
42
+ Requires-Dist: pytest>=8.0; extra == "dev"
43
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
44
+ Requires-Dist: responses>=0.25; extra == "dev"
45
+ Requires-Dist: black>=24.0; extra == "dev"
46
+ Requires-Dist: ruff>=0.4; extra == "dev"
47
+ Requires-Dist: pre-commit>=3.7; extra == "dev"
48
+ Requires-Dist: python-dotenv>=1.0; extra == "dev"
49
+ Dynamic: license-file
50
+
51
+ # Python FWF
52
+
53
+ [![PyPI Version](https://img.shields.io/pypi/v/python-fwf)](https://pypi.org/project/python-fwf/)
54
+ [![Python CI and PyPI Deploy](https://github.com/kelsoncm/python-fwf/actions/workflows/publish.yml/badge.svg)](https://github.com/kelsoncm/python-fwf/actions/workflows/publish.yml)
55
+ [![Python Versions](https://img.shields.io/pypi/pyversions/python-fwf.svg)](https://pypi.org/project/python-fwf/)
56
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
57
+ [![Tests](https://github.com/kelsoncm/python-fwf/actions/workflows/test.yml/badge.svg)](https://github.com/kelsoncm/python-fwf/actions/workflows/test.yml)
58
+ [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit)
59
+ [![Coverage](https://codecov.io/gh/kelsoncm/python-fwf/branch/main/graph/badge.svg)](https://codecov.io/gh/kelsoncm/python-fwf)
60
+
61
+
62
+ Python library for reading and manipulating **fixed-width files (FWF)**.
63
+
64
+ ## Features
65
+
66
+ - 📖 Read fixed-width format files with custom column definitions
67
+ - 🔧 Support for typed columns (integer, decimal, date, time, etc.)
68
+ - 📋 Header and footer row handling
69
+ - 🎯 Simple and intuitive API
70
+ - ✅ Fully tested (100% coverage)
71
+
72
+ ## Installation
73
+
74
+ ```bash
75
+ pip install pyfwf
76
+ ```
77
+
78
+ ## Quick Start
79
+
80
+ ```python
81
+ from pyfwf.columns import CharColumn, PositiveIntegerColumn
82
+ from pyfwf.descriptors import DetailRowDescriptor, FileDescriptor
83
+ from pyfwf.readers import Reader
84
+
85
+ # Define columns
86
+ detail = DetailRowDescriptor([
87
+ CharColumn(name='name', pos=1, size=20),
88
+ PositiveIntegerColumn(name='age', pos=21, size=3),
89
+ ])
90
+
91
+ # Create file descriptor
92
+ fd = FileDescriptor(line_size=23, details=[detail])
93
+
94
+ # Read file
95
+ with open('data.fwf', 'r') as f:
96
+ reader = Reader(f, fd)
97
+ for row in reader:
98
+ print(row)
99
+ ```
100
+
101
+ ## Supported Column Types
102
+
103
+ - `CharColumn` - Text data
104
+ - `RightCharColumn` - Right-aligned text
105
+ - `PositiveIntegerColumn` - Positive integers
106
+ - `PositiveDecimalColumn` - Decimal numbers
107
+ - `DateColumn` - Date values
108
+ - `TimeColumn` - Time values
109
+ - `DateTimeColumn` - DateTime values
110
+
111
+ ## Documentation
112
+
113
+ See [docs/](docs/) for detailed documentation and examples.
114
+
115
+ ## License
116
+
117
+ MIT License © 2015 Umbrella Tech
118
+
119
+ ## Author
120
+
121
+ Kelson da Costa Medeiros <kelsoncm@gmail.com>
@@ -0,0 +1,11 @@
1
+ pyfwf/__init__.py,sha256=Wb7sizlpkPQHXq2zf7grp-9YY7Mxram7EoOcR2FEmnk,1148
2
+ pyfwf/columns.py,sha256=gE7oyil_l3NP8KQPLIsgDQkpKCSnP-VIpSsr3jTKjcI,8638
3
+ pyfwf/descriptors.py,sha256=ysVGsnoI-LJGnvsL4OKSIqFp8-lm0LtW-I_CjssXJX0,4185
4
+ pyfwf/hydrating.py,sha256=jjltdWoagfgyYK7U5Q7TKko1H2NMj2Jq-P_hJWQFmfY,4023
5
+ pyfwf/readers.py,sha256=u-Yg4u7ZsHPOz4jx5aH_gUi3SFBtpEHfMt8o_H425Sc,3755
6
+ pyfwf/renders.py,sha256=276NHTihjJtHgEjT4_pvjj502y1JiGuYorsYr1xIvHc,2961
7
+ pyfwf-1.0.0.dist-info/licenses/LICENSE.md,sha256=Ua0hpApaWay1AcvGvjg3fwF8oPX4wS8Qp8s8qN6VOcc,1076
8
+ pyfwf-1.0.0.dist-info/METADATA,sha256=YuuQxjdhMebbGDGcpYgPyiFLvB5K0u3TAdgGt7l_RmM,4607
9
+ pyfwf-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
10
+ pyfwf-1.0.0.dist-info/top_level.txt,sha256=S1Nj1uO3Wl86B1rCeozK6iBwnp7c4c7IwiUEBQ551pc,6
11
+ pyfwf-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 kelsoncm
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1 @@
1
+ pyfwf