python-dwca-reader 0.16.2__py3-none-any.whl → 0.16.3__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.
dwca/read.py CHANGED
@@ -14,7 +14,12 @@ from pandas.io.parsers import TextFileReader
14
14
 
15
15
  import dwca.vendor
16
16
  from dwca.descriptors import ArchiveDescriptor, DataFileDescriptor, shorten_term
17
- from dwca.exceptions import RowNotFound, InvalidArchive, InvalidSimpleArchive, NotADataFile
17
+ from dwca.exceptions import (
18
+ RowNotFound,
19
+ InvalidArchive,
20
+ InvalidSimpleArchive,
21
+ NotADataFile,
22
+ )
18
23
  from dwca.files import CSVDataFile
19
24
  from dwca.helpers import remove_tree
20
25
  from dwca.rows import CoreRow
@@ -72,7 +77,7 @@ class DwCAReader(object):
72
77
 
73
78
  default_metadata_filename = "EML.xml"
74
79
  default_metafile_name = "meta.xml"
75
- source_metadata_directory = 'dataset'
80
+ source_metadata_directory = "dataset"
76
81
 
77
82
  def __enter__(self):
78
83
  return self
@@ -80,7 +85,13 @@ class DwCAReader(object):
80
85
  def __exit__(self, exc_type, exc_value, exc_traceback):
81
86
  self.close()
82
87
 
83
- def __init__(self, path: str, extensions_to_ignore: List[str] = None, tmp_dir: str = None) -> None:
88
+ def __init__(
89
+ self,
90
+ path: str,
91
+ extensions_to_ignore: List[str] = None,
92
+ tmp_dir: str = None,
93
+ skip_metadata: bool = False,
94
+ ) -> None:
84
95
  """Open the Darwin Core Archive."""
85
96
  if extensions_to_ignore is None:
86
97
  extensions_to_ignore = []
@@ -92,7 +103,9 @@ class DwCAReader(object):
92
103
  #: The path to the Darwin Core Archive file, as passed to the constructor.
93
104
  self.archive_path = path # type: str
94
105
 
95
- if os.path.isdir(self.archive_path): # Archive is a (directly readable) directory
106
+ if os.path.isdir(
107
+ self.archive_path
108
+ ): # Archive is a (directly readable) directory
96
109
  self._working_directory_path = self.archive_path
97
110
  self._directory_to_clean = None # type: Optional[str]
98
111
  else: # Archive is zipped/tgzipped, we have to extract it first.
@@ -104,15 +117,18 @@ class DwCAReader(object):
104
117
  self._metafile_handle = None
105
118
  try:
106
119
  self._metafile_handle = self.open_included_file(self.default_metafile_name)
107
- self.descriptor = ArchiveDescriptor(self._metafile_handle.read(),
108
- files_to_ignore=extensions_to_ignore)
120
+ self.descriptor = ArchiveDescriptor(
121
+ self._metafile_handle.read(), files_to_ignore=extensions_to_ignore
122
+ )
109
123
  except IOError as exc:
110
124
  if exc.errno == ENOENT:
111
125
  pass
112
126
 
113
127
  #: A :class:`xml.etree.ElementTree.Element` instance containing the (scientific) metadata
114
- #: of the archive, or `None` if the archive has no metadata.
115
- self.metadata = self._parse_metadata_file() # type: Optional[Element]
128
+ #: of the archive, or `None` if the archive has no metadata or if the `skip_metadata` parameter is True.
129
+ self.metadata = None # type: Optional[Element]
130
+ if not skip_metadata:
131
+ self.metadata = self._parse_metadata_file() # type: Optional[Element]
116
132
 
117
133
  #: If the archive contains source-level metadata (typically, GBIF downloads), this is a dict such as::
118
134
  #:
@@ -122,23 +138,33 @@ class DwCAReader(object):
122
138
  #: See :doc:`gbif_results` for more details.
123
139
  self.source_metadata = self._get_source_metadata() # type: Dict[str, Element]
124
140
 
125
- if self.descriptor: # We have an Archive descriptor that we can use to access data files.
141
+ if (
142
+ self.descriptor
143
+ ): # We have an Archive descriptor that we can use to access data files.
126
144
  #: An instance of :class:`dwca.files.CSVDataFile` for the core data file.
127
- self.core_file = CSVDataFile(self._working_directory_path, self.descriptor.core) # type: CSVDataFile
145
+ self.core_file = CSVDataFile(
146
+ self._working_directory_path, self.descriptor.core
147
+ ) # type: CSVDataFile
128
148
 
129
149
  #: A list of :class:`dwca.files.CSVDataFile`, one entry for each extension data file , sorted by order of
130
150
  #: appearance in the Metafile (or an empty list if the archive doesn't use extensions).
131
- self.extension_files = [CSVDataFile(work_directory=self._working_directory_path,
132
- file_descriptor=d)
133
- for d in self.descriptor.extensions] # type: List[CSVDataFile]
151
+ self.extension_files = [
152
+ CSVDataFile(
153
+ work_directory=self._working_directory_path, file_descriptor=d
154
+ )
155
+ for d in self.descriptor.extensions
156
+ ] # type: List[CSVDataFile]
134
157
  else: # Archive without descriptor, we'll have to find and inspect the data file
135
158
  try:
136
159
  datafile_name = self._is_valid_simple_archive()
137
160
  descriptor = DataFileDescriptor.make_from_file(
138
- os.path.join(self._working_directory_path, datafile_name))
161
+ os.path.join(self._working_directory_path, datafile_name)
162
+ )
139
163
 
140
- self.core_file = CSVDataFile(work_directory=self._working_directory_path,
141
- file_descriptor=descriptor)
164
+ self.core_file = CSVDataFile(
165
+ work_directory=self._working_directory_path,
166
+ file_descriptor=descriptor,
167
+ )
142
168
  self.extension_files = []
143
169
  except InvalidSimpleArchive:
144
170
  msg = "No Metafile was found, but the archive contains multiple files/directories."
@@ -146,14 +172,17 @@ class DwCAReader(object):
146
172
 
147
173
  def _get_source_metadata(self) -> Dict[str, Element]:
148
174
  source_metadata = {} # type: Dict[str, Element]
149
- source_metadata_dir = os.path.join(self._working_directory_path, self.source_metadata_directory)
175
+ source_metadata_dir = os.path.join(
176
+ self._working_directory_path, self.source_metadata_directory
177
+ )
150
178
 
151
179
  if os.path.isdir(source_metadata_dir):
152
180
  for f in os.listdir(source_metadata_dir):
153
181
  if os.path.isfile(os.path.join(source_metadata_dir, f)):
154
182
  dataset_key = os.path.splitext(f)[0]
155
183
  source_metadata[dataset_key] = self._parse_xml_included_file(
156
- os.path.join(self.source_metadata_directory, f))
184
+ os.path.join(self.source_metadata_directory, f)
185
+ )
157
186
 
158
187
  return source_metadata
159
188
 
@@ -192,23 +221,27 @@ class DwCAReader(object):
192
221
  not supported in case the value returned by `pandas.read_csv()` is a `TextFileReader` (e.g. when using
193
222
  `chunksize` or `iterator=True`).
194
223
  """
195
- datafile_descriptor = self.get_descriptor_for(relative_path) # type: DataFileDescriptor
224
+ datafile_descriptor = self.get_descriptor_for(
225
+ relative_path
226
+ ) # type: DataFileDescriptor
196
227
 
197
228
  if not dwca.vendor._has_pandas:
198
229
  raise ImportError("Pandas is missing.")
199
230
 
200
231
  from pandas import read_csv
201
232
 
202
- kwargs['delimiter'] = datafile_descriptor.fields_terminated_by
203
- kwargs['skiprows'] = datafile_descriptor.lines_to_ignore
204
- kwargs['header'] = None
205
- kwargs['names'] = datafile_descriptor.short_headers
233
+ kwargs["delimiter"] = datafile_descriptor.fields_terminated_by
234
+ kwargs["skiprows"] = datafile_descriptor.lines_to_ignore
235
+ kwargs["header"] = None
236
+ kwargs["names"] = datafile_descriptor.short_headers
206
237
 
207
- df_or_textreader = read_csv(self.absolute_temporary_path(relative_path), **kwargs)
238
+ df_or_textreader = read_csv(
239
+ self.absolute_temporary_path(relative_path), **kwargs
240
+ )
208
241
 
209
242
  # Add a column for default values, if present in the file descriptor
210
243
  for field in datafile_descriptor.fields:
211
- field_default_value = field['default']
244
+ field_default_value = field["default"]
212
245
  if field_default_value is not None:
213
246
  if isinstance(df_or_textreader, TextFileReader):
214
247
  # I don't see how to assign default values to a TextFileReader, so
@@ -220,7 +253,7 @@ class DwCAReader(object):
220
253
  "the archive."
221
254
  )
222
255
 
223
- df_or_textreader[shorten_term(field['term'])] = field_default_value
256
+ df_or_textreader[shorten_term(field["term"])] = field_default_value
224
257
 
225
258
  return df_or_textreader
226
259
 
@@ -249,7 +282,9 @@ class DwCAReader(object):
249
282
  ids = temp_ids.keys()
250
283
 
251
284
  for extension in self.extension_files:
252
- coreid_index = dict((k, v.tolist()) for k, v in extension.coreid_index.items())
285
+ coreid_index = dict(
286
+ (k, v.tolist()) for k, v in extension.coreid_index.items()
287
+ )
253
288
  for id in ids:
254
289
  coreid_index.pop(id, None)
255
290
  indexes[extension.file_descriptor.file_location] = coreid_index
@@ -311,7 +346,7 @@ class DwCAReader(object):
311
346
  - The position is often an appropriate way to unambiguously identify a core row in a DwCA.
312
347
 
313
348
  """
314
- for (i, row) in enumerate(self):
349
+ for i, row in enumerate(self):
315
350
  if i == position:
316
351
  return row
317
352
 
@@ -340,7 +375,9 @@ class DwCAReader(object):
340
375
  File existence is not tested.
341
376
 
342
377
  """
343
- return os.path.abspath(os.path.join(self._working_directory_path, relative_path))
378
+ return os.path.abspath(
379
+ os.path.join(self._working_directory_path, relative_path)
380
+ )
344
381
 
345
382
  def get_descriptor_for(self, relative_path: str) -> DataFileDescriptor:
346
383
  """Return a descriptor for the data file located at relative_path.
@@ -365,7 +402,6 @@ class DwCAReader(object):
365
402
  raise NotADataFile("{fn} is not a data file".format(fn=relative_path))
366
403
 
367
404
  def _is_valid_simple_archive(self) -> str:
368
-
369
405
  # If the working dir appear to contains a valid simple darwin core archive
370
406
  # (one single data file + possibly some metadata), returns the name of the data file.
371
407
  #
@@ -396,7 +432,7 @@ class DwCAReader(object):
396
432
  """Load the archive (scientific) Metadata file, parse it with\
397
433
  ElementTree and return its content (or `None` if the archive has no metadata).
398
434
 
399
- :raises: :class:`dwca.exceptions.InvalidArchive` if the archive references an non-existent
435
+ :raises: :class:`dwca.exceptions.InvalidArchive` if the archive references a non-existent
400
436
  metadata file.
401
437
  """
402
438
  # If the archive has descriptor, look for the metadata filename there.
@@ -407,14 +443,18 @@ class DwCAReader(object):
407
443
  return self._parse_xml_included_file(filename)
408
444
  except IOError as exc:
409
445
  if exc.errno == ENOENT: # File not found
410
- msg = "{} is referenced in the archive descriptor but missing.".format(filename)
446
+ msg = "{} is referenced in the archive descriptor but missing.".format(
447
+ filename
448
+ )
411
449
  raise InvalidArchive(msg)
412
450
 
413
451
  else: # Otherwise, the metadata file has to be named 'EML.xml'
414
452
  try:
415
453
  return self._parse_xml_included_file(self.default_metadata_filename)
416
454
  except IOError as exc:
417
- if exc.errno == ENOENT: # File not found, this is an archive without metadata
455
+ if (
456
+ exc.errno == ENOENT
457
+ ): # File not found, this is an archive without metadata
418
458
  return None
419
459
 
420
460
  assert False # For MyPy, see: https://github.com/python/mypy/issues/4223#issuecomment-342865133
@@ -428,7 +468,7 @@ class DwCAReader(object):
428
468
  # In that case, we work with a stripped string instead.
429
469
  # Note that this method cannot be generalized because it won't work well with encoding specified in the xml
430
470
  # tag. This is why we're only choosing it in case of error
431
- data_as_string = self.open_included_file(relative_path, 'r').read()
471
+ data_as_string = self.open_included_file(relative_path, "r").read()
432
472
  return ET.fromstring(data_as_string.strip())
433
473
 
434
474
  def _unzip_or_untar(self) -> str:
@@ -444,14 +484,16 @@ class DwCAReader(object):
444
484
  try:
445
485
  # Security note: with Python < 2.7.4, a zip file may be able to write outside of the
446
486
  # directory using absolute paths, parent (..) path, ... See note in ZipFile.extract doc
447
- zipfile.ZipFile(self.archive_path, 'r').extractall(tmp_dir)
487
+ zipfile.ZipFile(self.archive_path, "r").extractall(tmp_dir)
448
488
  except zipfile.BadZipfile:
449
489
  # Doesn't look like a valid zip, let's see if it's a tar archive (possibly compressed)
450
490
  try:
451
491
  # TODO: Once we only support Python 3.12+, we should pass the filter="data" argument to extractall()
452
- tarfile.open(self.archive_path, 'r:*').extractall(tmp_dir)
492
+ tarfile.open(self.archive_path, "r:*").extractall(tmp_dir)
453
493
  except tarfile.ReadError:
454
- raise InvalidArchive("The archive cannot be read. Is it a .zip or .tgz file?")
494
+ raise InvalidArchive(
495
+ "The archive cannot be read. Is it a .zip or .tgz file?"
496
+ )
455
497
 
456
498
  return tmp_dir
457
499
 
@@ -487,7 +529,7 @@ class DwCAReader(object):
487
529
  extension_file.close()
488
530
  if self._metafile_handle:
489
531
  self._metafile_handle.close()
490
-
532
+
491
533
  if self._directory_to_clean:
492
534
  remove_tree(self._directory_to_clean)
493
535
 
@@ -495,7 +537,7 @@ class DwCAReader(object):
495
537
  """Return `True` if the Core file of the archive contains the `term_url` term."""
496
538
  return term_url in self.core_file.file_descriptor.terms
497
539
 
498
- def __iter__(self) -> 'DwCAReader':
540
+ def __iter__(self) -> "DwCAReader":
499
541
  self._corefile_pointer = 0
500
542
  return self
501
543
 
dwca/rows.py CHANGED
@@ -16,36 +16,45 @@ class Row(object):
16
16
 
17
17
  # Common ground for __str__ between subclasses
18
18
  def _build_str(self, source_str, id_str):
19
- txt = ("--\n"
20
- "Rowtype: {rowtype}\n"
21
- "Position: {position}\n"
22
- "Source: {source_str}\n"
23
- "{id_row}\n"
24
- "Reference extension rows: {extension_flag}\n"
25
- "Reference source metadata: {source_metadata_flag}\n"
26
- "Data: {data}\n"
27
- "--\n")
28
-
29
- extension_flag = "Yes" if (hasattr(self, 'extensions') and (len(self.extensions) > 0)) else "No"
30
-
31
- if hasattr(self, 'source_metadata') and (self.source_metadata is not None):
32
- source_metadata_flag = 'Yes'
19
+ txt = (
20
+ "--\n"
21
+ "Rowtype: {rowtype}\n"
22
+ "Position: {position}\n"
23
+ "Source: {source_str}\n"
24
+ "{id_row}\n"
25
+ "Reference extension rows: {extension_flag}\n"
26
+ "Reference source metadata: {source_metadata_flag}\n"
27
+ "Data: {data}\n"
28
+ "--\n"
29
+ )
30
+
31
+ extension_flag = (
32
+ "Yes"
33
+ if (hasattr(self, "extensions") and (len(self.extensions) > 0))
34
+ else "No"
35
+ )
36
+
37
+ if hasattr(self, "source_metadata") and (self.source_metadata is not None):
38
+ source_metadata_flag = "Yes"
33
39
  else:
34
- source_metadata_flag = 'No'
35
-
36
- return txt.format(rowtype=self.rowtype,
37
- position=self.position,
38
- source_str=source_str,
39
- data=self.data,
40
- id_row=id_str,
41
- extension_flag=extension_flag,
42
- source_metadata_flag=source_metadata_flag)
43
-
44
- def __init__(self, csv_line: str, position: int, datafile_descriptor: DataFileDescriptor) -> None:
45
-
40
+ source_metadata_flag = "No"
41
+
42
+ return txt.format(
43
+ rowtype=self.rowtype,
44
+ position=self.position,
45
+ source_str=source_str,
46
+ data=self.data,
47
+ id_row=id_str,
48
+ extension_flag=extension_flag,
49
+ source_metadata_flag=source_metadata_flag,
50
+ )
51
+
52
+ def __init__(
53
+ self, csv_line: str, position: int, datafile_descriptor: DataFileDescriptor
54
+ ) -> None:
46
55
  #: An instance of :class:`dwca.descriptors.DataFileDescriptor` describing the originating
47
56
  #: data file.
48
- self.descriptor = datafile_descriptor # type: DataFileDescriptor
57
+ self.descriptor = datafile_descriptor # type: DataFileDescriptor
49
58
 
50
59
  #: The row position/index (starting at 0) in the source data file. This can be used, for example with
51
60
  #: :meth:`dwca.read.DwCAReader.get_corerow_by_position` or :meth:`dwca.files.CSVDataFile.get_row_by_position`.
@@ -59,10 +68,12 @@ class Row(object):
59
68
 
60
69
  # self.raw_fields is a list of the csv_line's content
61
70
  #:
62
- self.raw_fields = csv_line_to_fields(csv_line,
63
- line_ending=self.descriptor.lines_terminated_by,
64
- field_ending=self.descriptor.fields_terminated_by,
65
- fields_enclosed_by=self.descriptor.fields_enclosed_by)
71
+ self.raw_fields = csv_line_to_fields(
72
+ csv_line,
73
+ line_ending=self.descriptor.lines_terminated_by,
74
+ field_ending=self.descriptor.fields_terminated_by,
75
+ fields_enclosed_by=self.descriptor.fields_enclosed_by,
76
+ )
66
77
 
67
78
  # TODO: raw_fields is a new property: to test
68
79
 
@@ -84,18 +95,24 @@ class Row(object):
84
95
 
85
96
  for field_descriptor in self.descriptor.fields:
86
97
  try:
87
- column_index = int(field_descriptor['index'])
98
+ column_index = int(field_descriptor["index"])
88
99
  field_row_value = self.raw_fields[column_index]
89
100
  except TypeError:
90
101
  # int() argument must be a string... We don't have an index for this field
91
102
  field_row_value = None
92
103
  except IndexError:
93
- msg = 'The descriptor references a non-existent field (index={i})'.format(i=column_index)
104
+ msg = (
105
+ "The descriptor references a non-existent field (index={i})".format(
106
+ i=column_index
107
+ )
108
+ )
94
109
  raise InvalidArchive(msg)
95
110
 
96
- field_default_value = field_descriptor['default']
111
+ field_default_value = field_descriptor["default"]
97
112
 
98
- self.data[field_descriptor['term']] = field_row_value or field_default_value or ''
113
+ self.data[field_descriptor["term"]] = (
114
+ field_row_value or field_default_value or ""
115
+ )
99
116
 
100
117
 
101
118
  class CoreRow(Row):
@@ -110,7 +127,9 @@ class CoreRow(Row):
110
127
  id_str = "Row id: " + str(self.id)
111
128
  return super(CoreRow, self)._build_str("Core file", id_str)
112
129
 
113
- def __init__(self, csv_line: str, position: int, datafile_descriptor: DataFileDescriptor) -> None:
130
+ def __init__(
131
+ self, csv_line: str, position: int, datafile_descriptor: DataFileDescriptor
132
+ ) -> None:
114
133
  super(CoreRow, self).__init__(csv_line, position, datafile_descriptor)
115
134
 
116
135
  if self.descriptor.id_index is not None:
@@ -129,14 +148,14 @@ class CoreRow(Row):
129
148
  # provide some, but not for this row, it will be set to None).
130
149
  #
131
150
  # If this method is never called, the source_metadata attribute will not exist
132
- field_name = 'http://rs.tdwg.org/dwc/terms/datasetID'
151
+ field_name = "http://rs.tdwg.org/dwc/terms/datasetID"
133
152
 
134
153
  #: Row-level metadata (if provided by the archive).
135
154
  #: This is a non-standard DwCA feature currently that we can sometimes encounter (in downloads from GBIF.org
136
155
  #: for example).
137
156
  self.source_metadata = None
138
157
 
139
- if (archive_source_metadata and (field_name in self.data)):
158
+ if archive_source_metadata and (field_name in self.data):
140
159
  try:
141
160
  self.source_metadata = archive_source_metadata[self.data[field_name]]
142
161
  except KeyError:
@@ -150,10 +169,13 @@ class CoreRow(Row):
150
169
  # type () -> List[ExtensionRow]
151
170
  """A list of :class:`.ExtensionRow` instances that relates to this Core row."""
152
171
  # We use lazy loading
153
- if not hasattr(self, '_extensions'):
172
+ if not hasattr(self, "_extensions"):
154
173
  self._extensions = []
155
174
  for csv_file in self.extension_data_files:
156
- [self._extensions.append(r) for r in csv_file.get_all_rows_by_coreid(self.id)]
175
+ [
176
+ self._extensions.append(r)
177
+ for r in csv_file.get_all_rows_by_coreid(self.id)
178
+ ]
157
179
 
158
180
  return self._extensions
159
181
 
@@ -161,8 +183,16 @@ class CoreRow(Row):
161
183
  # Should these 3 be factorized ? How ? Mixin ? Parent class ?
162
184
  def __key(self):
163
185
  """Return a tuple representing the row. Common ground between equality and hash."""
164
- return (self.descriptor, self.id, self.data, self.extensions, self.source_metadata,
165
- self.rowtype, self.raw_fields, self.position)
186
+ return (
187
+ self.descriptor,
188
+ self.id,
189
+ self.data,
190
+ self.extensions,
191
+ self.source_metadata,
192
+ self.rowtype,
193
+ self.raw_fields,
194
+ self.position,
195
+ )
166
196
 
167
197
  def __eq__(self, other):
168
198
  return self.__key() == other.__key()
@@ -185,7 +215,9 @@ class ExtensionRow(Row):
185
215
  id_str = "Core row id: " + str(self.core_id)
186
216
  return super(ExtensionRow, self)._build_str("Extension file", id_str)
187
217
 
188
- def __init__(self, csv_line: str, position: int, datafile_descriptor: DataFileDescriptor) -> None:
218
+ def __init__(
219
+ self, csv_line: str, position: int, datafile_descriptor: DataFileDescriptor
220
+ ) -> None:
189
221
  super(ExtensionRow, self).__init__(csv_line, position, datafile_descriptor)
190
222
 
191
223
  #: The id of the core row this extension row is referring to.
@@ -193,7 +225,14 @@ class ExtensionRow(Row):
193
225
 
194
226
  def __key(self):
195
227
  """Return a tuple representing the row. Common ground between equality and hash."""
196
- return (self.descriptor, self.core_id, self.data, self.rowtype, self.raw_fields, self.position)
228
+ return (
229
+ self.descriptor,
230
+ self.core_id,
231
+ self.data,
232
+ self.rowtype,
233
+ self.raw_fields,
234
+ self.position,
235
+ )
197
236
 
198
237
  def __eq__(self, other):
199
238
  return self.__key() == other.__key()
@@ -214,10 +253,9 @@ def csv_line_to_fields(csv_line, line_ending, field_ending, fields_enclosed_by):
214
253
  raw_fields = []
215
254
 
216
255
  if fields_enclosed_by == "":
217
- opts = {'quoting': csv.QUOTE_NONE}
256
+ opts = {"quoting": csv.QUOTE_NONE}
218
257
  else:
219
- opts = {'quoting': csv.QUOTE_ALL,
220
- 'quotechar': fields_enclosed_by}
258
+ opts = {"quoting": csv.QUOTE_ALL, "quotechar": fields_enclosed_by}
221
259
 
222
260
  for row in csv.reader([csv_line], delimiter=field_ending, **opts):
223
261
  for f in row:
dwca/star_record.py CHANGED
@@ -5,16 +5,19 @@ import itertools
5
5
 
6
6
 
7
7
  class StarRecordIterator(object):
8
- """ Object used to iterate over multiple DWCA-files joined on the coreid
9
-
10
- :param files_to_join: a list of the `dwca.files.CSVDataFile`s we'd like to join.
11
- May or may not include the core file (the core is not treated in a special way)
12
- :param how: indicates the type of join. "inner" and "outer" correspond vaguely to
13
- inner and full joins. The outer join includes rows that don't match on all files,
14
- however, it doesn't create null fields to fill in when rows are missing in files.
15
- Attempts to conform to pandas.DataFrame.merge API.
8
+ """Object used to iterate over multiple DWCA-files joined on the coreid
9
+
10
+ :param files_to_join: a list of the `dwca.files.CSVDataFile`s we'd like to join.
11
+ May or may not include the core file (the core is not treated in a special way)
12
+ :param how: indicates the type of join. "inner" and "outer" correspond vaguely to
13
+ inner and full joins. The outer join includes rows that don't match on all files,
14
+ however, it doesn't create null fields to fill in when rows are missing in files.
15
+ Attempts to conform to pandas.DataFrame.merge API.
16
16
  """
17
- def __init__(self, files_to_join: List[CSVDataFile], how: Literal["inner", "outer"] = "inner"):
17
+
18
+ def __init__(
19
+ self, files_to_join: List[CSVDataFile], how: Literal["inner", "outer"] = "inner"
20
+ ):
18
21
  self.files_to_join = files_to_join
19
22
 
20
23
  # gather the coreids we want to join over.
@@ -26,12 +29,11 @@ class StarRecordIterator(object):
26
29
  # outer join: coreid may be in any files
27
30
  elif how == "outer":
28
31
  self.common_core |= set(data_file.coreid_index)
29
-
32
+
30
33
  # initialize iterator variables
31
34
  self._common_core_iterator = iter(self.common_core)
32
35
  self._cross_product_iterator = iter([])
33
36
 
34
-
35
37
  def __next__(self):
36
38
  # the next combination of rows matching this coreid
37
39
  next_positions = next(self._cross_product_iterator, None)
@@ -40,20 +42,26 @@ class StarRecordIterator(object):
40
42
  # get the next coreid
41
43
  self._current_coreid = next(self._common_core_iterator)
42
44
  self._files_with_current_coreid = [
43
- csv_file for csv_file in self.files_to_join
44
- if self._current_coreid in csv_file.coreid_index]
45
+ csv_file
46
+ for csv_file in self.files_to_join
47
+ if self._current_coreid in csv_file.coreid_index
48
+ ]
45
49
  # this iterates over all combinations of rows matching a coreid from all files
46
50
  self._cross_product_iterator = itertools.product(
47
51
  *(
48
- csv_file.coreid_index[self._current_coreid]
49
- for csv_file in self._files_with_current_coreid
50
- ))
52
+ csv_file.coreid_index[self._current_coreid]
53
+ for csv_file in self._files_with_current_coreid
54
+ )
55
+ )
51
56
  # go back and try to iterate over the rows for the new coreid
52
57
  return next(self)
53
58
  # zip up this combination of rows from all of the files.
54
59
  return (
55
- csv_file.get_row_by_position(position) for position, csv_file in zip(next_positions, self._files_with_current_coreid)
60
+ csv_file.get_row_by_position(position)
61
+ for position, csv_file in zip(
62
+ next_positions, self._files_with_current_coreid
63
+ )
56
64
  )
57
65
 
58
-
59
- def __iter__(self): return self
66
+ def __iter__(self):
67
+ return self
dwca/test/helpers.py CHANGED
@@ -4,4 +4,4 @@ import os
4
4
 
5
5
 
6
6
  def sample_data_path(filename):
7
- return os.path.join(os.path.dirname(__file__), 'sample_files', filename)
7
+ return os.path.join(os.path.dirname(__file__), "sample_files", filename)
@@ -35,13 +35,18 @@ class TestCSVDataFile(unittest.TestCase):
35
35
  description_txt = extension_files[0]
36
36
  vernacular_txt = extension_files[1]
37
37
 
38
- expected_core = {'1': array('L', [0]), '2': array('L', [1]), '3': array('L', [2]), '4': array('L', [3])}
38
+ expected_core = {
39
+ "1": array("L", [0]),
40
+ "2": array("L", [1]),
41
+ "3": array("L", [2]),
42
+ "4": array("L", [3]),
43
+ }
39
44
  assert core_txt.coreid_index == expected_core
40
45
 
41
- expected_vernacular = {"1": array('L', [0, 1, 2]), "2": array('L', [3])}
46
+ expected_vernacular = {"1": array("L", [0, 1, 2]), "2": array("L", [3])}
42
47
  assert vernacular_txt.coreid_index == expected_vernacular
43
48
 
44
- expected_description = {"1": array('L', [0, 1]), "4": array('L', [2])}
49
+ expected_description = {"1": array("L", [0, 1]), "4": array("L", [2])}
45
50
  assert description_txt.coreid_index == expected_description
46
51
 
47
52
  with pytest.raises(AttributeError):