datatypes 0.31.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.
- datatypes/__init__.py +169 -0
- datatypes/collections/__init__.py +25 -0
- datatypes/collections/container.py +248 -0
- datatypes/collections/mapping.py +1226 -0
- datatypes/collections/sequence.py +311 -0
- datatypes/command.py +596 -0
- datatypes/compat.py +206 -0
- datatypes/config/__init__.py +13 -0
- datatypes/config/environ/__init__.py +33 -0
- datatypes/config/environ/base.py +332 -0
- datatypes/config/environ/token.py +173 -0
- datatypes/config/parser.py +256 -0
- datatypes/config/settings.py +352 -0
- datatypes/copy.py +151 -0
- datatypes/csv.py +547 -0
- datatypes/datetime.py +1243 -0
- datatypes/decorators/__init__.py +31 -0
- datatypes/decorators/base.py +449 -0
- datatypes/decorators/descriptor.py +490 -0
- datatypes/decorators/misc.py +152 -0
- datatypes/email.py +439 -0
- datatypes/enum.py +176 -0
- datatypes/event.py +299 -0
- datatypes/exception.py +13 -0
- datatypes/filetypes/__init__.py +10 -0
- datatypes/filetypes/html.py +540 -0
- datatypes/filetypes/toml.py +275 -0
- datatypes/http.py +1443 -0
- datatypes/logging.py +475 -0
- datatypes/number.py +586 -0
- datatypes/parse.py +362 -0
- datatypes/path.py +4358 -0
- datatypes/profile.py +246 -0
- datatypes/reflection/__init__.py +24 -0
- datatypes/reflection/inspect.py +3475 -0
- datatypes/reflection/other.py +843 -0
- datatypes/reflection/path.py +639 -0
- datatypes/server.py +642 -0
- datatypes/service.py +212 -0
- datatypes/ssh.py +812 -0
- datatypes/string.py +1632 -0
- datatypes/token/__init__.py +16 -0
- datatypes/token/abnf.py +1696 -0
- datatypes/token/base.py +921 -0
- datatypes/token/word.py +259 -0
- datatypes/typing.py +126 -0
- datatypes/url.py +988 -0
- datatypes/utils.py +179 -0
- datatypes-0.31.0.dist-info/METADATA +38 -0
- datatypes-0.31.0.dist-info/RECORD +53 -0
- datatypes-0.31.0.dist-info/WHEEL +5 -0
- datatypes-0.31.0.dist-info/licenses/LICENSE.txt +21 -0
- datatypes-0.31.0.dist-info/top_level.txt +1 -0
datatypes/csv.py
ADDED
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import csv
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
import io
|
|
5
|
+
|
|
6
|
+
from .compat import *
|
|
7
|
+
from .config.environ import environ
|
|
8
|
+
from .string import String, ByteString
|
|
9
|
+
from .path import TempFilepath, Filepath
|
|
10
|
+
from . import logging
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CSVRowDict(dict):
|
|
17
|
+
"""An actual dict for each row
|
|
18
|
+
|
|
19
|
+
While this is about 50% slower than CSVRow, sometimes, like trying to
|
|
20
|
+
serialize a CSV row using orjson, you need an actual dict and not
|
|
21
|
+
something that just quacks like a dict. Basically, anything that is
|
|
22
|
+
using the Python C api PyDict_Next won't be able to "see" the CSVRow's
|
|
23
|
+
actual data so you have to take the speed hit and use something like
|
|
24
|
+
this class
|
|
25
|
+
|
|
26
|
+
.. Example:
|
|
27
|
+
# use a custom CSV row class
|
|
28
|
+
c = CSV("<CSV-PATH>", reader_row_class=CSVRowDict)
|
|
29
|
+
for row in c:
|
|
30
|
+
assert isinstance(row, dict)
|
|
31
|
+
"""
|
|
32
|
+
def __init__(self, columns, lookup):
|
|
33
|
+
super().__init__(zip(lookup.keys(), columns))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CSVRow(Mapping):
|
|
37
|
+
"""By default the CSV reader will return an instance of this class
|
|
38
|
+
|
|
39
|
+
This is done in `CSV.create_reader_row`. This exists because
|
|
40
|
+
csv.DictReader was quite a bit slower than expected in real world use,
|
|
41
|
+
using this sped up CSV reading by about 2.5-3x.
|
|
42
|
+
|
|
43
|
+
This walks and talks like a dict but it is not a traditional dict, that's
|
|
44
|
+
something to be aware of.
|
|
45
|
+
|
|
46
|
+
There is a bit of overhead for creating these instances, so if the most
|
|
47
|
+
performance is a necessity, you can override `CSV.create_reader_row` and
|
|
48
|
+
just return the columns, then you can manually grab columns using the
|
|
49
|
+
CSV instance's `.lookup` property exactly how this class does it
|
|
50
|
+
|
|
51
|
+
`Mapping` parent gives implementations for .__contains__, .items, .keys,
|
|
52
|
+
and .values
|
|
53
|
+
|
|
54
|
+
https://github.com/python/cpython/blob/3.11/Lib/_collections_abc.py#L754
|
|
55
|
+
"""
|
|
56
|
+
def __init__(self, columns, lookup):
|
|
57
|
+
"""
|
|
58
|
+
:param columns: list, the list of values of this row
|
|
59
|
+
:param lookup: dict[str, int], the keys are the field names and the
|
|
60
|
+
values are the index in `columns` of the value that corresponds
|
|
61
|
+
to that field name
|
|
62
|
+
"""
|
|
63
|
+
self.columns = columns
|
|
64
|
+
self.lookup = lookup
|
|
65
|
+
self.mutable = False
|
|
66
|
+
|
|
67
|
+
def _make_mutable(self):
|
|
68
|
+
"""Converts this row into a mutable row
|
|
69
|
+
|
|
70
|
+
If we're adding or removing fields then we need a local copy of
|
|
71
|
+
.lookup so we don't mess up any other rows in the CSV
|
|
72
|
+
"""
|
|
73
|
+
if not self.mutable:
|
|
74
|
+
self.lookup = {**self.lookup}
|
|
75
|
+
self.mutable = True
|
|
76
|
+
|
|
77
|
+
def __getitem__(self, k):
|
|
78
|
+
return self.columns[self.lookup[k]]
|
|
79
|
+
|
|
80
|
+
def __setitem__(self, k, v):
|
|
81
|
+
"""the value of .columns is updated with v if it already exists,
|
|
82
|
+
otherwise v is added to the end of .columns"""
|
|
83
|
+
if k in self.lookup:
|
|
84
|
+
self.columns[self.lookup[k]] = v
|
|
85
|
+
|
|
86
|
+
else:
|
|
87
|
+
self._make_mutable()
|
|
88
|
+
self.lookup[k] = len(self.columns)
|
|
89
|
+
self.columns.append(v)
|
|
90
|
+
|
|
91
|
+
def __delitem__(self, k):
|
|
92
|
+
"""This just sets the field's value in .columns to None and removes k
|
|
93
|
+
from .lookup"""
|
|
94
|
+
self._make_mutable()
|
|
95
|
+
i = self.lookup.pop(k)
|
|
96
|
+
self.columns[i] = None
|
|
97
|
+
|
|
98
|
+
def __iter__(self):
|
|
99
|
+
if self.columns:
|
|
100
|
+
yield from self.lookup
|
|
101
|
+
|
|
102
|
+
def __len__(self):
|
|
103
|
+
return len(self.lookup)
|
|
104
|
+
|
|
105
|
+
def pop(self, k, *default):
|
|
106
|
+
try:
|
|
107
|
+
v = self[k]
|
|
108
|
+
del self[k]
|
|
109
|
+
|
|
110
|
+
except KeyError:
|
|
111
|
+
if default:
|
|
112
|
+
v = default[0]
|
|
113
|
+
|
|
114
|
+
else:
|
|
115
|
+
raise
|
|
116
|
+
|
|
117
|
+
return v
|
|
118
|
+
|
|
119
|
+
def __bool__(self):
|
|
120
|
+
return bool(self.columns)
|
|
121
|
+
|
|
122
|
+
def popitem(self):
|
|
123
|
+
self._make_mutable()
|
|
124
|
+
k, i = self.lookup.popitem()
|
|
125
|
+
v = self.columns[i]
|
|
126
|
+
self.columns[i] = None
|
|
127
|
+
return k, v
|
|
128
|
+
|
|
129
|
+
def clear(self):
|
|
130
|
+
self._make_mutable()
|
|
131
|
+
self.lookup.clear()
|
|
132
|
+
self.columns = []
|
|
133
|
+
|
|
134
|
+
def __reversed__(self):
|
|
135
|
+
return reversed(list(self.keys()))
|
|
136
|
+
|
|
137
|
+
def copy(self):
|
|
138
|
+
return type(self)(self.columns, self.lookup)
|
|
139
|
+
|
|
140
|
+
def setdefault(self, k, v):
|
|
141
|
+
if k not in self:
|
|
142
|
+
self[k] = v
|
|
143
|
+
|
|
144
|
+
def update(self, *others, **kwargs):
|
|
145
|
+
for other in others:
|
|
146
|
+
if keys := getattr(other, "keys", None):
|
|
147
|
+
for k in keys():
|
|
148
|
+
self[k] = other[k]
|
|
149
|
+
|
|
150
|
+
else:
|
|
151
|
+
for k, v in other:
|
|
152
|
+
self[k] = v
|
|
153
|
+
|
|
154
|
+
for k, v in kwargs.items():
|
|
155
|
+
self[k] = v
|
|
156
|
+
|
|
157
|
+
def __or__(self, other):
|
|
158
|
+
r = self.copy()
|
|
159
|
+
r.update(other)
|
|
160
|
+
return r
|
|
161
|
+
|
|
162
|
+
def __ior__(self, other):
|
|
163
|
+
self.update(other)
|
|
164
|
+
return self
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class CSV(object):
|
|
168
|
+
"""Easily read/write the rows of a csv file
|
|
169
|
+
|
|
170
|
+
:Example:
|
|
171
|
+
# read a csv file
|
|
172
|
+
c = CSV("/path/to/read/file.csv")
|
|
173
|
+
for row in c:
|
|
174
|
+
print(row)
|
|
175
|
+
# write a csv file
|
|
176
|
+
c = CSV("/path/to/write/file.csv")
|
|
177
|
+
with c:
|
|
178
|
+
for row in rows:
|
|
179
|
+
c.add(row)
|
|
180
|
+
|
|
181
|
+
In testing, this is about 25-30% slower than the vanilla CSV reader but
|
|
182
|
+
gives you dict support among other niceties
|
|
183
|
+
|
|
184
|
+
https://docs.python.org/3/library/csv.html
|
|
185
|
+
"""
|
|
186
|
+
def __init__(self, path, fieldnames=None, encoding="", **kwargs):
|
|
187
|
+
"""Create the csv instance
|
|
188
|
+
:param path: str|IOBase, the path to the csv file that will be
|
|
189
|
+
read/written :param fieldnames: list, the fieldnames, when writing,
|
|
190
|
+
if this is. This can also already be a file pointer like object
|
|
191
|
+
omitted then the keys of the first row dictionary passed to .add()
|
|
192
|
+
will be used for the fieldnames. If omitted when reading then the
|
|
193
|
+
first line of the csv file will be used for the fieldnames
|
|
194
|
+
:param encoding: string, what character encoing to use
|
|
195
|
+
:param **kwargs:
|
|
196
|
+
strict: bool, pass in True (default False) to have the class check
|
|
197
|
+
fieldnames when writing.
|
|
198
|
+
https://docs.python.org/3/library/csv.html#csv.Dialect.strict
|
|
199
|
+
extrasaction: str, "ignore" (default when strict is False) to
|
|
200
|
+
ignore extra fields, "raise" (default when strict is True) to
|
|
201
|
+
raise an error
|
|
202
|
+
write_mode: str, the default mode that will be passed to open a
|
|
203
|
+
stream for writing
|
|
204
|
+
readonly: bool, True if writing operations should fail
|
|
205
|
+
"""
|
|
206
|
+
self.path = path
|
|
207
|
+
self.set_fieldnames(fieldnames)
|
|
208
|
+
self.writer = None
|
|
209
|
+
self.reader = None
|
|
210
|
+
self.strict = kwargs.pop("strict", False)
|
|
211
|
+
|
|
212
|
+
self.readonly = kwargs.pop("readonly", False)
|
|
213
|
+
if self.readonly:
|
|
214
|
+
self.write_mode = kwargs.pop("write_mode", "")
|
|
215
|
+
self.append_mode = kwargs.pop("append_mode", "")
|
|
216
|
+
|
|
217
|
+
else:
|
|
218
|
+
self.write_mode = kwargs.pop("write_mode", "w+")
|
|
219
|
+
self.append_mode = kwargs.pop("append_mode", "a+")
|
|
220
|
+
|
|
221
|
+
if not encoding:
|
|
222
|
+
encoding = environ.ENCODING
|
|
223
|
+
self.encoding = encoding
|
|
224
|
+
|
|
225
|
+
self.reader_row_class = kwargs.get("reader_row_class", CSVRow)
|
|
226
|
+
self.writer_row_class = kwargs.get("writer_row_class", None)
|
|
227
|
+
self.writer_class = kwargs.get("writer_class", csv.DictWriter)
|
|
228
|
+
self.reader_class = kwargs.get("reader_class", csv.reader)
|
|
229
|
+
|
|
230
|
+
def open(self, mode=""):
|
|
231
|
+
"""Mainly an internal method used for opening the file pointers needed
|
|
232
|
+
for reading and writing
|
|
233
|
+
|
|
234
|
+
:param mode: string, the open mode
|
|
235
|
+
:returns: file pointer
|
|
236
|
+
"""
|
|
237
|
+
if not mode or self.readonly:
|
|
238
|
+
mode = "r"
|
|
239
|
+
|
|
240
|
+
path = Filepath(self.path)
|
|
241
|
+
|
|
242
|
+
logger.debug("Opening csv file: {} using mode: {}".format(
|
|
243
|
+
path,
|
|
244
|
+
mode,
|
|
245
|
+
))
|
|
246
|
+
|
|
247
|
+
return path.open(mode=mode, encoding=self.encoding)
|
|
248
|
+
|
|
249
|
+
@contextmanager
|
|
250
|
+
def reading(self):
|
|
251
|
+
"""Internal method to manage reading operations
|
|
252
|
+
|
|
253
|
+
:returns: IOBase, the file pointer ready to be fully read
|
|
254
|
+
"""
|
|
255
|
+
if isinstance(self.path, io.IOBase):
|
|
256
|
+
#logger.debug("Reading io: {}".format(self.path))
|
|
257
|
+
tell = self.path.tell()
|
|
258
|
+
self.path.seek(0)
|
|
259
|
+
try:
|
|
260
|
+
yield self.path
|
|
261
|
+
|
|
262
|
+
finally:
|
|
263
|
+
self.path.seek(tell)
|
|
264
|
+
|
|
265
|
+
else:
|
|
266
|
+
with self.open() as stream:
|
|
267
|
+
yield stream
|
|
268
|
+
|
|
269
|
+
@contextmanager
|
|
270
|
+
def writing(self):
|
|
271
|
+
"""Internal method to manage writing operations
|
|
272
|
+
|
|
273
|
+
:returns: IOBase, the file pointer ready to be written to
|
|
274
|
+
"""
|
|
275
|
+
if self.readonly:
|
|
276
|
+
raise IOError("CSV is in readonly mode")
|
|
277
|
+
|
|
278
|
+
if isinstance(self.path, io.IOBase):
|
|
279
|
+
#logger.debug("Writing io: {}".format(self.path.name))
|
|
280
|
+
yield self.path
|
|
281
|
+
|
|
282
|
+
else:
|
|
283
|
+
with self.open(self.write_mode) as stream:
|
|
284
|
+
yield stream
|
|
285
|
+
|
|
286
|
+
@contextmanager
|
|
287
|
+
def appending(self):
|
|
288
|
+
"""The default context manager truncates and write a new file, but that
|
|
289
|
+
doesn't work for .add(), .append(), or .extend() so this provides an
|
|
290
|
+
alternative context manager for appending to the file
|
|
291
|
+
"""
|
|
292
|
+
prev_mode = self.write_mode
|
|
293
|
+
self.write_mode = self.append_mode
|
|
294
|
+
try:
|
|
295
|
+
with self:
|
|
296
|
+
yield self
|
|
297
|
+
|
|
298
|
+
finally:
|
|
299
|
+
self.write_mode = prev_mode
|
|
300
|
+
|
|
301
|
+
def __enter__(self):
|
|
302
|
+
"""Enables with context manager for writing"""
|
|
303
|
+
if not self.writer:
|
|
304
|
+
cm = self.writing()
|
|
305
|
+
self.writer = self.create_writer(cm.__enter__())
|
|
306
|
+
self.writer.cm = cm
|
|
307
|
+
# protection against multiple context managers
|
|
308
|
+
self.writer.context_depth = 0
|
|
309
|
+
|
|
310
|
+
self.writer.context_depth += 1
|
|
311
|
+
return self
|
|
312
|
+
|
|
313
|
+
def __exit__(self, exc_class, exc, trace):
|
|
314
|
+
self.writer.context_depth -= 1
|
|
315
|
+
if self.writer.context_depth <= 0:
|
|
316
|
+
self.writer.cm.__exit__(exc_class, exc, trace)
|
|
317
|
+
self.writer = None
|
|
318
|
+
|
|
319
|
+
def create_writer(self, stream, **kwargs):
|
|
320
|
+
kwargs.setdefault("dialect", csv.excel)
|
|
321
|
+
kwargs.setdefault("restval", "")
|
|
322
|
+
if self.strict:
|
|
323
|
+
kwargs.setdefault("strict", True)
|
|
324
|
+
kwargs.setdefault("extrasaction", "raise")
|
|
325
|
+
else:
|
|
326
|
+
kwargs.setdefault("strict", False)
|
|
327
|
+
kwargs.setdefault("extrasaction", "ignore")
|
|
328
|
+
kwargs.setdefault("quoting", csv.QUOTE_MINIMAL)
|
|
329
|
+
kwargs.setdefault("fieldnames", self.fieldnames)
|
|
330
|
+
|
|
331
|
+
writer = kwargs.pop("writer_class", self.writer_class)(
|
|
332
|
+
stream,
|
|
333
|
+
**kwargs
|
|
334
|
+
)
|
|
335
|
+
writer.has_header = True if stream.tell() > 0 else False
|
|
336
|
+
return writer
|
|
337
|
+
|
|
338
|
+
def create_reader(self, stream, **kwargs):
|
|
339
|
+
"""create a csv reader, this exists to make it easy to customize
|
|
340
|
+
functionality, for example, you might have a csv file that doesn't have
|
|
341
|
+
column headers, so you can override this method to pass in the column
|
|
342
|
+
names, etc.
|
|
343
|
+
|
|
344
|
+
:param stream: IOBase, usually a file path opened with open()
|
|
345
|
+
:returns: csv.Reader instance or something that acts like a built-in
|
|
346
|
+
csv.Reader instance
|
|
347
|
+
"""
|
|
348
|
+
kwargs.setdefault("dialect", csv.excel)
|
|
349
|
+
reader = kwargs.pop("reader_class", self.reader_class)(
|
|
350
|
+
stream,
|
|
351
|
+
**kwargs
|
|
352
|
+
)
|
|
353
|
+
return reader
|
|
354
|
+
|
|
355
|
+
def add(self, row):
|
|
356
|
+
"""Add one row to the end of the CSV file
|
|
357
|
+
|
|
358
|
+
This is the set interface
|
|
359
|
+
|
|
360
|
+
:param row: dict, the row to be written
|
|
361
|
+
"""
|
|
362
|
+
with self.appending():
|
|
363
|
+
writer = self.writer
|
|
364
|
+
if row := self.create_writer_row(row):
|
|
365
|
+
if not writer.has_header:
|
|
366
|
+
if not self.fieldnames:
|
|
367
|
+
self.set_fieldnames(row.keys())
|
|
368
|
+
|
|
369
|
+
writer.fieldnames = self.fieldnames
|
|
370
|
+
logger.debug("Writing fieldnames: {}".format(
|
|
371
|
+
", ".join(self.fieldnames)
|
|
372
|
+
))
|
|
373
|
+
writer.writeheader()
|
|
374
|
+
writer.has_header = True
|
|
375
|
+
|
|
376
|
+
writer.writerow(row)
|
|
377
|
+
|
|
378
|
+
def append(self, row):
|
|
379
|
+
"""Add one row to the end of the CSV file
|
|
380
|
+
|
|
381
|
+
This is the list interface
|
|
382
|
+
|
|
383
|
+
:param row: dict, the row to be written
|
|
384
|
+
"""
|
|
385
|
+
return self.add(row)
|
|
386
|
+
|
|
387
|
+
def extend(self, rows):
|
|
388
|
+
"""Add all the rows to the end of the csv file
|
|
389
|
+
|
|
390
|
+
This is the list interface
|
|
391
|
+
|
|
392
|
+
:param rows: list, all the rows
|
|
393
|
+
"""
|
|
394
|
+
with self.appending():
|
|
395
|
+
for row in rows:
|
|
396
|
+
self.add(row)
|
|
397
|
+
|
|
398
|
+
def update(self, rows):
|
|
399
|
+
"""Add all the rows to the end of the csv file
|
|
400
|
+
|
|
401
|
+
This is the set and dict interface
|
|
402
|
+
|
|
403
|
+
:param rows: list, all the rows
|
|
404
|
+
"""
|
|
405
|
+
return self.extend(rows)
|
|
406
|
+
|
|
407
|
+
def create_reader_row(self, columns, **kwargs):
|
|
408
|
+
"""prepare row for reading, meant to be overridden in child classes if
|
|
409
|
+
needed"""
|
|
410
|
+
#return {k: columns[self.lookup[k]] for k in self.lookup}
|
|
411
|
+
return kwargs.get("reader_row_class", self.reader_row_class)(
|
|
412
|
+
columns,
|
|
413
|
+
self.lookup
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
def create_writer_row(self, row, **kwargs):
|
|
417
|
+
"""prepare row for writing, meant to be overridden in child classes if
|
|
418
|
+
needed
|
|
419
|
+
"""
|
|
420
|
+
writer_row_class = kwargs.get(
|
|
421
|
+
"writer_row_class",
|
|
422
|
+
self.writer_row_class
|
|
423
|
+
)
|
|
424
|
+
if writer_row_class:
|
|
425
|
+
row = writer_row_class(row, self.lookup)
|
|
426
|
+
|
|
427
|
+
else:
|
|
428
|
+
def get_value(v):
|
|
429
|
+
if self.strict:
|
|
430
|
+
return ByteString(v)
|
|
431
|
+
|
|
432
|
+
else:
|
|
433
|
+
# NOTE -- for some reason, the internal writer treats b""
|
|
434
|
+
# and ByteString(b"") differently, the b"" would be written
|
|
435
|
+
# out as "b""" which make me think it's doing repr(value)
|
|
436
|
+
# internally or something
|
|
437
|
+
return ByteString(b"" if v is None else v)
|
|
438
|
+
|
|
439
|
+
row = {
|
|
440
|
+
String(k): get_value(v) for k, v in row.items()
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if self.strict:
|
|
444
|
+
rowcount = len(row)
|
|
445
|
+
fncount = len(self.fieldnames)
|
|
446
|
+
if rowcount != fncount:
|
|
447
|
+
raise ValueError(
|
|
448
|
+
"mismatch {} row(s) to {} fieldname(s)".format(
|
|
449
|
+
rowcount,
|
|
450
|
+
fncount
|
|
451
|
+
)
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
return row
|
|
455
|
+
|
|
456
|
+
def set_fieldnames(self, fieldnames):
|
|
457
|
+
"""Set the fieldnames and create the lookup table
|
|
458
|
+
|
|
459
|
+
:param fieldnames: list[str], a list of fieldnames that corresponds
|
|
460
|
+
to the columns in the CSV file
|
|
461
|
+
"""
|
|
462
|
+
if fieldnames:
|
|
463
|
+
self.fieldnames = list(map(String, fieldnames))
|
|
464
|
+
self.lookup = {item[1]: item[0] for item in enumerate(fieldnames)}
|
|
465
|
+
|
|
466
|
+
else:
|
|
467
|
+
self.fieldnames = []
|
|
468
|
+
self.lookup = {}
|
|
469
|
+
|
|
470
|
+
def find_fieldnames(self):
|
|
471
|
+
"""attempt to get the field names from the first line in the csv file
|
|
472
|
+
"""
|
|
473
|
+
if self.fieldnames:
|
|
474
|
+
return self.fieldnames
|
|
475
|
+
|
|
476
|
+
else:
|
|
477
|
+
for row in self:
|
|
478
|
+
return self.fieldnames
|
|
479
|
+
|
|
480
|
+
def rows(self):
|
|
481
|
+
"""Return all the rows as a list"""
|
|
482
|
+
return self.tolist()
|
|
483
|
+
|
|
484
|
+
def tolist(self):
|
|
485
|
+
"""Return all the rows as a list
|
|
486
|
+
|
|
487
|
+
Here for consistent interface as I'm using .tolist elsewhere
|
|
488
|
+
|
|
489
|
+
:returns: list
|
|
490
|
+
"""
|
|
491
|
+
return list(self.__iter__())
|
|
492
|
+
|
|
493
|
+
def clear(self):
|
|
494
|
+
"""clear the csv file"""
|
|
495
|
+
with self.writing() as stream:
|
|
496
|
+
stream.truncate(0)
|
|
497
|
+
|
|
498
|
+
def __iter__(self):
|
|
499
|
+
with self.reading() as stream:
|
|
500
|
+
ignore_row = True
|
|
501
|
+
reader = self.create_reader(stream)
|
|
502
|
+
for columns in reader:
|
|
503
|
+
if ignore_row:
|
|
504
|
+
if self.fieldnames:
|
|
505
|
+
# Check to see if the fieldnames are the first row of
|
|
506
|
+
# the CSV file, if they are then ignore this row
|
|
507
|
+
for i, col in enumerate(columns):
|
|
508
|
+
if self.fieldnames[i] != col:
|
|
509
|
+
ignore_row = False
|
|
510
|
+
break
|
|
511
|
+
|
|
512
|
+
else:
|
|
513
|
+
# if we don't have field names then the first row
|
|
514
|
+
# has to be the fieldnames
|
|
515
|
+
self.set_fieldnames(columns)
|
|
516
|
+
|
|
517
|
+
if not ignore_row:
|
|
518
|
+
ignore_row = False
|
|
519
|
+
row = self.create_reader_row(columns)
|
|
520
|
+
if row is not None:
|
|
521
|
+
yield row
|
|
522
|
+
|
|
523
|
+
def __len__(self):
|
|
524
|
+
"""Returns how many rows are in this csv, this actually goes through
|
|
525
|
+
all the rows, so this is not a resource light method"""
|
|
526
|
+
count = 0
|
|
527
|
+
for r in self:
|
|
528
|
+
count += 1
|
|
529
|
+
return count
|
|
530
|
+
|
|
531
|
+
def __str__(self):
|
|
532
|
+
return self.read_text()
|
|
533
|
+
|
|
534
|
+
def read_text(self):
|
|
535
|
+
"""This will print out all rows so it might not be ideal to use for
|
|
536
|
+
bigger CSVs"""
|
|
537
|
+
with self.reading() as stream:
|
|
538
|
+
return stream.read()
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
class TempCSV(CSV):
|
|
542
|
+
"""Create a temporary CSV file in the system's tempdir"""
|
|
543
|
+
def __init__(self, fieldnames=None, **kwargs):
|
|
544
|
+
path = TempFilepath(kwargs.pop("path", ""), dir=kwargs.pop("dir", ""))
|
|
545
|
+
kwargs["fieldnames"] = fieldnames
|
|
546
|
+
super().__init__(path, **kwargs)
|
|
547
|
+
|