datatypes 0.12.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 +153 -0
- datatypes/collections/__init__.py +22 -0
- datatypes/collections/container.py +150 -0
- datatypes/collections/mapping.py +614 -0
- datatypes/collections/sequence.py +469 -0
- datatypes/command.py +572 -0
- datatypes/compat.py +205 -0
- datatypes/config/__init__.py +9 -0
- datatypes/config/environ.py +302 -0
- datatypes/config/parser.py +255 -0
- datatypes/copy.py +151 -0
- datatypes/csv.py +322 -0
- datatypes/datetime.py +888 -0
- datatypes/decorators/__init__.py +23 -0
- datatypes/decorators/base.py +343 -0
- datatypes/decorators/descriptor.py +389 -0
- datatypes/decorators/misc.py +118 -0
- datatypes/email.py +351 -0
- datatypes/enum.py +191 -0
- datatypes/event.py +207 -0
- datatypes/filetypes/__init__.py +12 -0
- datatypes/filetypes/html.py +556 -0
- datatypes/filetypes/toml.py +260 -0
- datatypes/http.py +723 -0
- datatypes/logging.py +384 -0
- datatypes/number.py +187 -0
- datatypes/parse.py +246 -0
- datatypes/path.py +3637 -0
- datatypes/profile.py +232 -0
- datatypes/reflection.py +1498 -0
- datatypes/server.py +408 -0
- datatypes/service.py +212 -0
- datatypes/string.py +1328 -0
- datatypes/token/__init__.py +16 -0
- datatypes/token/abnf.py +1704 -0
- datatypes/token/base.py +475 -0
- datatypes/token/word.py +259 -0
- datatypes/url.py +841 -0
- datatypes/utils.py +142 -0
- datatypes-0.12.0.dist-info/LICENSE.txt +21 -0
- datatypes-0.12.0.dist-info/METADATA +52 -0
- datatypes-0.12.0.dist-info/RECORD +44 -0
- datatypes-0.12.0.dist-info/WHEEL +5 -0
- datatypes-0.12.0.dist-info/top_level.txt +1 -0
datatypes/csv.py
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from __future__ import unicode_literals, division, print_function, absolute_import
|
|
3
|
+
import os
|
|
4
|
+
import codecs
|
|
5
|
+
import csv
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
|
|
8
|
+
from .compat import *
|
|
9
|
+
from .config.environ import environ
|
|
10
|
+
from .string import String, ByteString
|
|
11
|
+
from .utils import cbany
|
|
12
|
+
from .path import TempFilepath
|
|
13
|
+
from . import logging
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CSV(object):
|
|
20
|
+
"""Easily read/write the rows of a csv file
|
|
21
|
+
:Example:
|
|
22
|
+
# read a csv file
|
|
23
|
+
c = CSV("/path/to/read/file.csv")
|
|
24
|
+
for row in c:
|
|
25
|
+
print(row)
|
|
26
|
+
# write a csv file
|
|
27
|
+
c = CSV("/path/to/write/file.csv")
|
|
28
|
+
with c:
|
|
29
|
+
for row in rows:
|
|
30
|
+
c.add(row)
|
|
31
|
+
|
|
32
|
+
https://docs.python.org/3/library/csv.html
|
|
33
|
+
"""
|
|
34
|
+
reader_class = csv.DictReader
|
|
35
|
+
"""the class that will be used by .create_reader()"""
|
|
36
|
+
|
|
37
|
+
writer_class = csv.DictWriter
|
|
38
|
+
"""the class that will be used by .create_writer()"""
|
|
39
|
+
|
|
40
|
+
rows_class = list
|
|
41
|
+
"""the class used in .rows()"""
|
|
42
|
+
|
|
43
|
+
reader_row_class = None
|
|
44
|
+
"""You can set this to a class and rows returned from the default .normalize_reader_row()
|
|
45
|
+
will be this type"""
|
|
46
|
+
|
|
47
|
+
writer_row_class = None
|
|
48
|
+
"""You can set this to a class and rows returned from the default .normalize_writer_row()
|
|
49
|
+
will be this type, this class should act like a dict unless you also change .writer_class"""
|
|
50
|
+
|
|
51
|
+
class ContinueError(Exception):
|
|
52
|
+
"""Can be thrown to have CSV skip the current row"""
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
def __init__(self, path, fieldnames=None, encoding="", **kwargs):
|
|
56
|
+
"""Create the csv instance
|
|
57
|
+
:param path: string, the path to the csv file that will be read/written
|
|
58
|
+
:param fieldnames: list, the fieldnames, when writing, if this is omitted
|
|
59
|
+
then the keys of the first row dictionary passed to .add() will be used
|
|
60
|
+
for the fieldnames. If omitted when reading then the first line of the
|
|
61
|
+
csv file will be used for the fieldnames
|
|
62
|
+
:param encoding: string, what character encoing to use
|
|
63
|
+
:param **kwargs:
|
|
64
|
+
strict -- bool, pass in True (default False) to have the class check
|
|
65
|
+
fieldnames when writing.
|
|
66
|
+
https://docs.python.org/3/library/csv.html#csv.Dialect.strict
|
|
67
|
+
extrasaction -- str, "ignore" (default when strict is False) to ignore extra
|
|
68
|
+
fields, "raise" (default when strict is True) to raise an error
|
|
69
|
+
"""
|
|
70
|
+
self.path = path
|
|
71
|
+
self.fieldnames = self.normalize_fieldnames(fieldnames or [])
|
|
72
|
+
self.writer = None
|
|
73
|
+
self.reader = None
|
|
74
|
+
self.context_depth = 0 # protection against multiple context managers
|
|
75
|
+
self.strict = kwargs.pop("strict", False)
|
|
76
|
+
self.writer_mode = kwargs.pop("writer_mode", "wb+")
|
|
77
|
+
|
|
78
|
+
if not encoding:
|
|
79
|
+
encoding = environ.ENCODING
|
|
80
|
+
self.encoding = encoding
|
|
81
|
+
|
|
82
|
+
cls = type(self)
|
|
83
|
+
for k, v in kwargs.items():
|
|
84
|
+
if hasattr(cls, k):
|
|
85
|
+
setattr(self, k, v)
|
|
86
|
+
|
|
87
|
+
def __iter__(self):
|
|
88
|
+
logger.debug("Reading csv file: {}".format(self.path))
|
|
89
|
+
|
|
90
|
+
with self.open() as f:
|
|
91
|
+
first_row = True
|
|
92
|
+
self.reader = self.create_reader(f)
|
|
93
|
+
for row in self.reader:
|
|
94
|
+
try:
|
|
95
|
+
row = self.normalize_reader_row(row)
|
|
96
|
+
if row:
|
|
97
|
+
if self.fieldnames and first_row:
|
|
98
|
+
# if you've passed in fieldnames then it will return
|
|
99
|
+
# fieldnames mapped to fieldnames as the first row,
|
|
100
|
+
# this will catch that and ignore it
|
|
101
|
+
#
|
|
102
|
+
# if we pass in fieldnames then DictReader won't use the first
|
|
103
|
+
# row as fieldnames, so we need to check to make sure the first
|
|
104
|
+
# row isn't a field_name: field_name mapping
|
|
105
|
+
if cbany(lambda r: r[0] != r[1], row.items()):
|
|
106
|
+
yield row
|
|
107
|
+
|
|
108
|
+
else:
|
|
109
|
+
yield row
|
|
110
|
+
|
|
111
|
+
except self.ContinueError:
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
finally:
|
|
115
|
+
first_row = False
|
|
116
|
+
|
|
117
|
+
def open(self, mode=""):
|
|
118
|
+
"""Mainly an internal method used for opening the file pointers needed for
|
|
119
|
+
reading and writing
|
|
120
|
+
|
|
121
|
+
:param mode: string, the open mode
|
|
122
|
+
:returns: file pointer
|
|
123
|
+
"""
|
|
124
|
+
if not mode:
|
|
125
|
+
mode = "r"
|
|
126
|
+
return codecs.open(self.path, encoding=self.encoding, mode=mode)
|
|
127
|
+
|
|
128
|
+
@contextmanager
|
|
129
|
+
def appending(self, mode="ab+"):
|
|
130
|
+
"""The default context manager truncates and write a new file, but that
|
|
131
|
+
doesn't work for .add(), .append(), or .extend() so this provides an
|
|
132
|
+
alternative context manager allows for appending to the file
|
|
133
|
+
|
|
134
|
+
:param mode: str, the append mode that will be used to create the
|
|
135
|
+
writer
|
|
136
|
+
"""
|
|
137
|
+
prev_mode = self.writer_mode
|
|
138
|
+
self.writer_mode = mode
|
|
139
|
+
with self:
|
|
140
|
+
yield self
|
|
141
|
+
|
|
142
|
+
self.writer_mode = prev_mode
|
|
143
|
+
|
|
144
|
+
def __enter__(self):
|
|
145
|
+
"""Enables with context manager for writing"""
|
|
146
|
+
self.context_depth += 1
|
|
147
|
+
if not self.writer:
|
|
148
|
+
logger.debug("Writing csv file: {} using mode: {}".format(
|
|
149
|
+
self.path,
|
|
150
|
+
self.writer_mode,
|
|
151
|
+
))
|
|
152
|
+
f = self.open(self.writer_mode)
|
|
153
|
+
self.writer = self.create_writer(f)
|
|
154
|
+
return self
|
|
155
|
+
|
|
156
|
+
def __exit__(self, exception_type, exception_val, trace):
|
|
157
|
+
self.context_depth -= 1
|
|
158
|
+
if self.context_depth <= 0:
|
|
159
|
+
self.writer.f.close()
|
|
160
|
+
self.writer = None
|
|
161
|
+
self.context_depth = 0
|
|
162
|
+
|
|
163
|
+
def create_writer(self, f, **kwargs):
|
|
164
|
+
kwargs.setdefault("dialect", csv.excel)
|
|
165
|
+
kwargs.setdefault("restval", "")
|
|
166
|
+
if self.strict:
|
|
167
|
+
kwargs.setdefault("strict", True)
|
|
168
|
+
kwargs.setdefault("extrasaction", "raise")
|
|
169
|
+
else:
|
|
170
|
+
kwargs.setdefault("strict", False)
|
|
171
|
+
kwargs.setdefault("extrasaction", "ignore")
|
|
172
|
+
kwargs.setdefault("quoting", csv.QUOTE_MINIMAL)
|
|
173
|
+
kwargs.setdefault("fieldnames", self.fieldnames)
|
|
174
|
+
|
|
175
|
+
# from testdata CSVpath code:
|
|
176
|
+
# in order to make unicode csvs work we are going to do a round about
|
|
177
|
+
# thing where we write to a string buffer and then pull that out and write
|
|
178
|
+
# it to the file, this is the only way I can make utf-8 work
|
|
179
|
+
queue = self.normalize_writer_file(f)
|
|
180
|
+
writer = self.writer_class(queue, **kwargs)
|
|
181
|
+
writer.f = f
|
|
182
|
+
writer.queue = queue
|
|
183
|
+
writer.has_header = True if os.path.getsize(self.path) > 0 else False
|
|
184
|
+
return writer
|
|
185
|
+
|
|
186
|
+
def create_reader(self, f, **kwargs):
|
|
187
|
+
"""create a csv reader, this exists to make it easy to customize functionality,
|
|
188
|
+
for example, you might have a csv file that doesn't have column headers, so you
|
|
189
|
+
can override this method to pass in the column names, etc.
|
|
190
|
+
:param f: io object, usually a file path opened with open()
|
|
191
|
+
:returns: csv.Reader instance or something that acts like a built-in csv.Reader
|
|
192
|
+
instance
|
|
193
|
+
"""
|
|
194
|
+
kwargs.setdefault("fieldnames", self.normalize_reader_fieldnames(self.fieldnames or None))
|
|
195
|
+
kwargs.setdefault("dialect", csv.excel)
|
|
196
|
+
|
|
197
|
+
f = self.normalize_reader_file(f)
|
|
198
|
+
reader = self.reader_class(f, **kwargs)
|
|
199
|
+
reader.f = f
|
|
200
|
+
return reader
|
|
201
|
+
|
|
202
|
+
def normalize_writer_file(self, f):
|
|
203
|
+
queue = StringIO()
|
|
204
|
+
return queue
|
|
205
|
+
|
|
206
|
+
def normalize_reader_file(self, f):
|
|
207
|
+
# https://stackoverflow.com/a/30031962/5006
|
|
208
|
+
class FileWrapper(object):
|
|
209
|
+
def __init__(self, f):
|
|
210
|
+
self.f = f
|
|
211
|
+
self.last_line = "" # will contain raw CSV row
|
|
212
|
+
|
|
213
|
+
def __iter__(self):
|
|
214
|
+
return self
|
|
215
|
+
|
|
216
|
+
def __next__(self):
|
|
217
|
+
self.last_line = next(self.f)
|
|
218
|
+
return self.last_line
|
|
219
|
+
next = __next__ # needed for py2? Python didn't consider it an iterator without next()
|
|
220
|
+
|
|
221
|
+
return FileWrapper(f)
|
|
222
|
+
|
|
223
|
+
def normalize_reader_row(self, row):
|
|
224
|
+
"""prepare row for reading, meant to be overridden in child classes if needed"""
|
|
225
|
+
if self.reader_row_class:
|
|
226
|
+
row = self.reader_row_class(row)
|
|
227
|
+
|
|
228
|
+
return row
|
|
229
|
+
|
|
230
|
+
def normalize_writer_row(self, row):
|
|
231
|
+
"""prepare row for writing, meant to be overridden in child classes if needed"""
|
|
232
|
+
row = {String(k): ByteString(v) for k, v in row.items()}
|
|
233
|
+
|
|
234
|
+
if self.strict:
|
|
235
|
+
rowcount = len(row)
|
|
236
|
+
fncount = len(self.fieldnames)
|
|
237
|
+
if rowcount != fncount:
|
|
238
|
+
raise ValueError("mismatch {} row(s) to {} fieldname(s)".format(rowcount, fncount))
|
|
239
|
+
|
|
240
|
+
if self.writer_row_class:
|
|
241
|
+
row = self.writer_row_class(row)
|
|
242
|
+
|
|
243
|
+
return row
|
|
244
|
+
|
|
245
|
+
def add(self, row):
|
|
246
|
+
with self.appending():
|
|
247
|
+
writer = self.writer
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
row = self.normalize_writer_row(row)
|
|
251
|
+
if row:
|
|
252
|
+
if not writer.has_header:
|
|
253
|
+
if not self.fieldnames:
|
|
254
|
+
self.fieldnames = self.normalize_fieldnames(row.keys())
|
|
255
|
+
writer.fieldnames = self.normalize_writer_fieldnames(self.fieldnames)
|
|
256
|
+
logger.debug("Writing fieldnames: {}".format(", ".join(self.fieldnames)))
|
|
257
|
+
writer.writeheader()
|
|
258
|
+
writer.has_header = True
|
|
259
|
+
|
|
260
|
+
writer.writerow(row)
|
|
261
|
+
data = writer.queue.getvalue()
|
|
262
|
+
writer.f.write(data)
|
|
263
|
+
writer.queue.truncate(0)
|
|
264
|
+
writer.queue.seek(0)
|
|
265
|
+
|
|
266
|
+
except self.ContinueError:
|
|
267
|
+
pass
|
|
268
|
+
|
|
269
|
+
def append(self, rows):
|
|
270
|
+
"""Add all the rows to the end of the csv file
|
|
271
|
+
|
|
272
|
+
:param rows: list, all the rows
|
|
273
|
+
"""
|
|
274
|
+
with self.appending():
|
|
275
|
+
for row in rows:
|
|
276
|
+
self.add(row)
|
|
277
|
+
|
|
278
|
+
def extend(self, rows):
|
|
279
|
+
return self.append(rows)
|
|
280
|
+
|
|
281
|
+
def normalize_fieldnames(self, fieldnames):
|
|
282
|
+
"""run this anytime fields are going to be set on this instance"""
|
|
283
|
+
return list(map(String, fieldnames))
|
|
284
|
+
|
|
285
|
+
def normalize_writer_fieldnames(self, fieldnames):
|
|
286
|
+
"""run this right before setting fieldnames onto the writer instance"""
|
|
287
|
+
return fieldnames
|
|
288
|
+
|
|
289
|
+
def normalize_reader_fieldnames(self, fieldnames):
|
|
290
|
+
"""run this right before setting fieldnames onto the reader instance"""
|
|
291
|
+
return fieldnames
|
|
292
|
+
|
|
293
|
+
def find_fieldnames(self):
|
|
294
|
+
"""attempt to get the field names from the first line in the csv file"""
|
|
295
|
+
with self.open() as f:
|
|
296
|
+
reader = self.create_reader(f)
|
|
297
|
+
return list(map(String, reader.fieldnames))
|
|
298
|
+
|
|
299
|
+
def rows(self):
|
|
300
|
+
"""Return all the rows as a list"""
|
|
301
|
+
return self.rows_class(self.__iter__())
|
|
302
|
+
|
|
303
|
+
def clear(self):
|
|
304
|
+
"""clear the csv file"""
|
|
305
|
+
with self.open("wb") as f:
|
|
306
|
+
f.truncate(0)
|
|
307
|
+
|
|
308
|
+
def __len__(self):
|
|
309
|
+
"""Returns how many rows are in this csv, this actually goes through all the
|
|
310
|
+
rows, so this is not a resource light method"""
|
|
311
|
+
count = 0
|
|
312
|
+
for r in self:
|
|
313
|
+
count += 1
|
|
314
|
+
return count
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class TempCSV(CSV):
|
|
318
|
+
def __init__(self, fieldnames=None, **kwargs):
|
|
319
|
+
path = TempFilepath(kwargs.pop("path", ""), dir=kwargs.pop("dir", ""))
|
|
320
|
+
kwargs["fieldnames"] = fieldnames
|
|
321
|
+
super().__init__(path, **kwargs)
|
|
322
|
+
|