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.
Files changed (44) hide show
  1. datatypes/__init__.py +153 -0
  2. datatypes/collections/__init__.py +22 -0
  3. datatypes/collections/container.py +150 -0
  4. datatypes/collections/mapping.py +614 -0
  5. datatypes/collections/sequence.py +469 -0
  6. datatypes/command.py +572 -0
  7. datatypes/compat.py +205 -0
  8. datatypes/config/__init__.py +9 -0
  9. datatypes/config/environ.py +302 -0
  10. datatypes/config/parser.py +255 -0
  11. datatypes/copy.py +151 -0
  12. datatypes/csv.py +322 -0
  13. datatypes/datetime.py +888 -0
  14. datatypes/decorators/__init__.py +23 -0
  15. datatypes/decorators/base.py +343 -0
  16. datatypes/decorators/descriptor.py +389 -0
  17. datatypes/decorators/misc.py +118 -0
  18. datatypes/email.py +351 -0
  19. datatypes/enum.py +191 -0
  20. datatypes/event.py +207 -0
  21. datatypes/filetypes/__init__.py +12 -0
  22. datatypes/filetypes/html.py +556 -0
  23. datatypes/filetypes/toml.py +260 -0
  24. datatypes/http.py +723 -0
  25. datatypes/logging.py +384 -0
  26. datatypes/number.py +187 -0
  27. datatypes/parse.py +246 -0
  28. datatypes/path.py +3637 -0
  29. datatypes/profile.py +232 -0
  30. datatypes/reflection.py +1498 -0
  31. datatypes/server.py +408 -0
  32. datatypes/service.py +212 -0
  33. datatypes/string.py +1328 -0
  34. datatypes/token/__init__.py +16 -0
  35. datatypes/token/abnf.py +1704 -0
  36. datatypes/token/base.py +475 -0
  37. datatypes/token/word.py +259 -0
  38. datatypes/url.py +841 -0
  39. datatypes/utils.py +142 -0
  40. datatypes-0.12.0.dist-info/LICENSE.txt +21 -0
  41. datatypes-0.12.0.dist-info/METADATA +52 -0
  42. datatypes-0.12.0.dist-info/RECORD +44 -0
  43. datatypes-0.12.0.dist-info/WHEEL +5 -0
  44. 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
+