ogc-na 0.3.55__tar.gz → 0.3.57__tar.gz

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.

Potentially problematic release.


This version of ogc-na might be problematic. Click here for more details.

Files changed (61) hide show
  1. {ogc_na-0.3.55 → ogc_na-0.3.57}/PKG-INFO +3 -2
  2. {ogc_na-0.3.55 → ogc_na-0.3.57}/ogc/na/_version.py +2 -2
  3. {ogc_na-0.3.55 → ogc_na-0.3.57}/ogc/na/annotate_schema.py +38 -11
  4. ogc_na-0.3.57/ogc/na/input_filters/xlsx.py +87 -0
  5. {ogc_na-0.3.55 → ogc_na-0.3.57}/ogc_na.egg-info/PKG-INFO +3 -2
  6. {ogc_na-0.3.55 → ogc_na-0.3.57}/ogc_na.egg-info/SOURCES.txt +6 -0
  7. {ogc_na-0.3.55 → ogc_na-0.3.57}/ogc_na.egg-info/requires.txt +1 -0
  8. {ogc_na-0.3.55 → ogc_na-0.3.57}/requirements.txt +1 -0
  9. ogc_na-0.3.57/test/data/headers.xlsx +0 -0
  10. ogc_na-0.3.57/test/data/no-headers.xlsx +0 -0
  11. ogc_na-0.3.57/test/data/schema-anchors.json +29 -0
  12. ogc_na-0.3.57/test/data/two-sheets.xlsx +0 -0
  13. {ogc_na-0.3.55 → ogc_na-0.3.57}/test/test_annotate_schema.py +13 -1
  14. ogc_na-0.3.57/test/test_input_filters_xlsx.py +94 -0
  15. {ogc_na-0.3.55 → ogc_na-0.3.57}/.github/workflows/mkdocs.yml +0 -0
  16. {ogc_na-0.3.55 → ogc_na-0.3.57}/.github/workflows/python-publish.yml +0 -0
  17. {ogc_na-0.3.55 → ogc_na-0.3.57}/.gitignore +0 -0
  18. {ogc_na-0.3.55 → ogc_na-0.3.57}/MANIFEST.in +0 -0
  19. {ogc_na-0.3.55 → ogc_na-0.3.57}/README.md +0 -0
  20. {ogc_na-0.3.55 → ogc_na-0.3.57}/docs/examples.md +0 -0
  21. {ogc_na-0.3.55 → ogc_na-0.3.57}/docs/gen_ref_pages.py +0 -0
  22. {ogc_na-0.3.55 → ogc_na-0.3.57}/docs/index.md +0 -0
  23. {ogc_na-0.3.55 → ogc_na-0.3.57}/docs/jsonld-uplift.md +0 -0
  24. {ogc_na-0.3.55 → ogc_na-0.3.57}/docs/tutorials.md +0 -0
  25. {ogc_na-0.3.55 → ogc_na-0.3.57}/mkdocs.yml +0 -0
  26. {ogc_na-0.3.55 → ogc_na-0.3.57}/ogc/na/__init__.py +0 -0
  27. {ogc_na-0.3.55 → ogc_na-0.3.57}/ogc/na/domain_config.py +0 -0
  28. {ogc_na-0.3.55 → ogc_na-0.3.57}/ogc/na/download.py +0 -0
  29. {ogc_na-0.3.55 → ogc_na-0.3.57}/ogc/na/exceptions.py +0 -0
  30. {ogc_na-0.3.55 → ogc_na-0.3.57}/ogc/na/gsp.py +0 -0
  31. {ogc_na-0.3.55 → ogc_na-0.3.57}/ogc/na/ingest_json.py +0 -0
  32. {ogc_na-0.3.55 → ogc_na-0.3.57}/ogc/na/input_filters/__init__.py +0 -0
  33. {ogc_na-0.3.55 → ogc_na-0.3.57}/ogc/na/input_filters/csv.py +0 -0
  34. {ogc_na-0.3.55 → ogc_na-0.3.57}/ogc/na/input_filters/xml.py +0 -0
  35. {ogc_na-0.3.55 → ogc_na-0.3.57}/ogc/na/models.py +0 -0
  36. {ogc_na-0.3.55 → ogc_na-0.3.57}/ogc/na/profile.py +0 -0
  37. {ogc_na-0.3.55 → ogc_na-0.3.57}/ogc/na/provenance.py +0 -0
  38. {ogc_na-0.3.55 → ogc_na-0.3.57}/ogc/na/update_vocabs.py +0 -0
  39. {ogc_na-0.3.55 → ogc_na-0.3.57}/ogc/na/util.py +0 -0
  40. {ogc_na-0.3.55 → ogc_na-0.3.57}/ogc/na/validation.py +0 -0
  41. {ogc_na-0.3.55 → ogc_na-0.3.57}/ogc_na.egg-info/dependency_links.txt +0 -0
  42. {ogc_na-0.3.55 → ogc_na-0.3.57}/ogc_na.egg-info/top_level.txt +0 -0
  43. {ogc_na-0.3.55 → ogc_na-0.3.57}/pyproject.toml +0 -0
  44. {ogc_na-0.3.55 → ogc_na-0.3.57}/rdf/catalog-v001.xml +0 -0
  45. {ogc_na-0.3.55 → ogc_na-0.3.57}/rdf/domaincfg.vocab.ttl +0 -0
  46. {ogc_na-0.3.55 → ogc_na-0.3.57}/setup.cfg +0 -0
  47. {ogc_na-0.3.55 → ogc_na-0.3.57}/setup.py +0 -0
  48. {ogc_na-0.3.55 → ogc_na-0.3.57}/test/__init__.py +0 -0
  49. {ogc_na-0.3.55 → ogc_na-0.3.57}/test/data/empty.ttl +0 -0
  50. {ogc_na-0.3.55 → ogc_na-0.3.57}/test/data/headers.csv +0 -0
  51. {ogc_na-0.3.55 → ogc_na-0.3.57}/test/data/no-headers.csv +0 -0
  52. {ogc_na-0.3.55 → ogc_na-0.3.57}/test/data/profile_tree.ttl +0 -0
  53. {ogc_na-0.3.55 → ogc_na-0.3.57}/test/data/profile_tree_cyclic.ttl +0 -0
  54. {ogc_na-0.3.55 → ogc_na-0.3.57}/test/data/sample-context.jsonld +0 -0
  55. {ogc_na-0.3.55 → ogc_na-0.3.57}/test/data/sample-schema-prop-c.yml +0 -0
  56. {ogc_na-0.3.55 → ogc_na-0.3.57}/test/data/sample-schema.yml +0 -0
  57. {ogc_na-0.3.55 → ogc_na-0.3.57}/test/data/schema-vocab.yml +0 -0
  58. {ogc_na-0.3.55 → ogc_na-0.3.57}/test/data/uplift_context_valid.yml +0 -0
  59. {ogc_na-0.3.55 → ogc_na-0.3.57}/test/test_ingest_json.py +0 -0
  60. {ogc_na-0.3.55 → ogc_na-0.3.57}/test/test_input_filters_csv.py +0 -0
  61. {ogc_na-0.3.55 → ogc_na-0.3.57}/test/test_profile.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: ogc_na
3
- Version: 0.3.55
3
+ Version: 0.3.57
4
4
  Summary: OGC Naming Authority tools
5
5
  Author-email: Rob Atkinson <ratkinson@ogc.org>, Piotr Zaborowski <pzaborowski@ogc.org>, Alejandro Villar <avillar@ogc.org>
6
6
  Project-URL: Homepage, https://github.com/opengeospatial/ogc-na-tools/
@@ -27,6 +27,7 @@ Requires-Dist: rfc3987
27
27
  Requires-Dist: requests-cache
28
28
  Requires-Dist: xmltodict
29
29
  Requires-Dist: jsonpointer~=2.4
30
+ Requires-Dist: openpyxl~=3.1.5
30
31
  Requires-Dist: setuptools
31
32
  Provides-Extra: dev
32
33
  Requires-Dist: mkdocs>=1.4.2; extra == "dev"
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.3.55'
16
- __version_tuple__ = version_tuple = (0, 3, 55)
15
+ __version__ = version = '0.3.57'
16
+ __version_tuple__ = version_tuple = (0, 3, 57)
@@ -123,6 +123,7 @@ import json
123
123
  import logging
124
124
  import re
125
125
  import sys
126
+ from collections import deque
126
127
  from operator import attrgetter
127
128
  from pathlib import Path
128
129
  from typing import Any, AnyStr, Callable, Sequence, Iterable
@@ -177,6 +178,7 @@ class ReferencedSchema:
177
178
  chain: list = dataclasses.field(default_factory=list)
178
179
  ref: str | Path = None
179
180
  is_json: bool = False
181
+ anchors: dict[str, Any] = dataclasses.field(default_factory=dict)
180
182
 
181
183
 
182
184
  @dataclasses.dataclass
@@ -192,8 +194,27 @@ class SchemaResolver:
192
194
  self._schema_cache: dict[str | Path, Any] = {}
193
195
 
194
196
  @staticmethod
195
- def _get_branch(schema: dict, ref: str):
196
- return jsonpointer.resolve_pointer(schema, re.sub('^#', '', ref))
197
+ def _get_branch(schema: dict, ref: str, anchors: dict[str, Any] = None):
198
+ ref = re.sub('^#', '', ref)
199
+ if anchors and ref in anchors:
200
+ return anchors[ref]
201
+ return jsonpointer.resolve_pointer(schema, ref)
202
+
203
+ @staticmethod
204
+ def _find_anchors(schema: dict) -> dict[str, Any]:
205
+ anchors = {}
206
+
207
+ pending = deque((schema,))
208
+ while pending:
209
+ current = pending.popleft()
210
+ if isinstance(current, dict):
211
+ if '$anchor' in current:
212
+ anchors[current['$anchor']] = current
213
+ pending.extend(current.values())
214
+ elif isinstance(current, list):
215
+ pending.extend(current)
216
+
217
+ return anchors
197
218
 
198
219
  def load_contents(self, s: str | Path) -> tuple[dict, bool]:
199
220
  """
@@ -252,11 +273,13 @@ class SchemaResolver:
252
273
  raise ValueError('Local ref provided without an anchor: ' + ref)
253
274
  return ReferencedSchema(location=from_schema.location,
254
275
  fragment=ref[1:],
255
- subschema=SchemaResolver._get_branch(from_schema.full_contents, ref),
276
+ subschema=SchemaResolver._get_branch(from_schema.full_contents, ref,
277
+ from_schema.anchors),
256
278
  full_contents=from_schema.full_contents,
257
279
  chain=chain,
258
280
  ref=ref,
259
- is_json=from_schema.is_json)
281
+ is_json=from_schema.is_json,
282
+ anchors=from_schema.anchors)
260
283
 
261
284
  if force_contents:
262
285
  is_json = False
@@ -269,20 +292,23 @@ class SchemaResolver:
269
292
  contents = force_contents
270
293
  else:
271
294
  contents, is_json = self.load_contents(schema_source)
295
+ anchors = SchemaResolver._find_anchors(contents)
272
296
  if fragment:
273
297
  return ReferencedSchema(location=schema_source, fragment=fragment,
274
- subschema=SchemaResolver._get_branch(contents, fragment),
298
+ subschema=SchemaResolver._get_branch(contents, fragment, anchors),
275
299
  full_contents=contents,
276
300
  chain=chain,
277
301
  ref=ref,
278
- is_json=is_json)
302
+ is_json=is_json,
303
+ anchors=anchors)
279
304
  else:
280
305
  return ReferencedSchema(location=schema_source,
281
306
  subschema=contents,
282
307
  full_contents=contents,
283
308
  chain=chain,
284
309
  ref=ref,
285
- is_json=is_json)
310
+ is_json=is_json,
311
+ anchors=anchors)
286
312
  except Exception as e:
287
313
  f = f" from {from_schema.location}" if from_schema else ''
288
314
  raise IOError(f"Error resolving reference {ref}{f}") from e
@@ -522,7 +548,7 @@ class SchemaAnnotator:
522
548
  if vocab and ':' not in prop_ctx and prop_ctx not in JSON_LD_KEYWORDS:
523
549
  prop_ctx = f"{vocab}{prop_ctx}"
524
550
  return {'@id': prop_ctx}
525
- elif '@id' not in prop_ctx and not vocab:
551
+ elif '@id' not in prop_ctx and '@reverse' not in prop_ctx and not vocab:
526
552
  raise ValueError(f'Missing @id for property {prop} in context {json.dumps(ctx, indent=2)}')
527
553
  else:
528
554
  result = {k: v for k, v in prop_ctx.items() if k in JSON_LD_KEYWORDS}
@@ -751,10 +777,11 @@ class ContextBuilder:
751
777
  self._missed_properties[full_property_path_str] = False
752
778
  prop_context['@' + term[len(ANNOTATION_PREFIX):]] = term_val
753
779
 
754
- if isinstance(prop_context.get('@id'), str):
755
- self.visited_properties[full_property_path_str] = prop_context['@id']
780
+ if isinstance(prop_context.get('@id'), str) or isinstance(prop_context.get('@reverse'), str):
781
+ prop_id_value = prop_context.get('@id', prop_context.get('@reverse'))
782
+ self.visited_properties[full_property_path_str] = prop_id_value
756
783
  self._missed_properties[full_property_path_str] = False
757
- if prop_context['@id'] in ('@nest', '@graph'):
784
+ if prop_id_value in ('@nest', '@graph'):
758
785
  merge_contexts(onto_context, process_subschema(prop_val, from_schema, full_property_path))
759
786
  else:
760
787
  merge_contexts(prop_context['@context'],
@@ -0,0 +1,87 @@
1
+ """
2
+ Excel (XLSX) Input filter for ingest_json.
3
+
4
+ Processes Excel XLSX files with [openpyxl](https://openpyxl.readthedocs.io/en/stable/).
5
+
6
+ Configuration values:
7
+
8
+ * `worksheet` (default: `None`): The name of the worksheet to process. If `None`, the default one will be used.
9
+ * `rows` (default: `dict`): type of elements in the result list:
10
+ * `dict`: elements will be dictionaries, with the keys taken from the `header-row`.
11
+ * `list`: each resulting row will be an array values.
12
+ * `header-row` (default: `0`): if `rows` is `dict`, the (0-based) index of the header row. All rows before the
13
+ header row will be skipped.
14
+ * `skip-rows` (default: `0`): number of rows to skip at the beginning of the file (apart from the header and pre-header
15
+ ones if `rows` is `dict`).
16
+ * `skip-empty-rows` (default: `True`): whether to omit empty rows (i.e., those with no values) from the result.
17
+ """
18
+ from __future__ import annotations
19
+
20
+ from datetime import datetime
21
+ from io import BytesIO
22
+ from typing import Any
23
+ from openpyxl import load_workbook
24
+ from openpyxl.cell import Cell
25
+
26
+ from ogc.na import util
27
+
28
+ DEFAULT_CONF = {
29
+ 'worksheet': None,
30
+ 'rows': 'dict',
31
+ 'header-row': 0,
32
+ 'skip-rows': 0,
33
+ 'skip-empty-rows': True,
34
+ }
35
+
36
+
37
+ def _cell_to_json(c: Cell) -> str | float | int | None:
38
+ if isinstance(c.value, datetime):
39
+ return c.value.isoformat()
40
+ return c.value
41
+
42
+
43
+ def apply_filter(content: bytes, conf: dict[str, Any] | None) -> tuple[dict[str, Any] | list, dict[str, Any] | None]:
44
+ conf = util.deep_update(DEFAULT_CONF, conf) if conf else DEFAULT_CONF
45
+
46
+ metadata = {
47
+ 'filter': {
48
+ 'conf': conf,
49
+ },
50
+ }
51
+
52
+ wb = load_workbook(filename=BytesIO(content), read_only=True)
53
+ if conf['worksheet']:
54
+ ws = wb[conf['worksheet']]
55
+ else:
56
+ ws = wb.worksheets[0]
57
+ rows = ws.rows
58
+ metadata['worksheet'] = ws.title
59
+
60
+ headers = None
61
+ if conf['rows'] == 'dict':
62
+ header_row = max(conf['header-row'], 0)
63
+ # Skip to header row
64
+ for i in range(header_row):
65
+ next(rows, None)
66
+ headers = next(rows, [])
67
+ if not headers:
68
+ return [], None
69
+ else:
70
+ headers = [_cell_to_json(c) for c in headers]
71
+ metadata['headers'] = headers
72
+
73
+ for i in range(conf['skip-rows']):
74
+ next(rows, None)
75
+
76
+ result = []
77
+ for row in rows:
78
+ row_values = [_cell_to_json(c) for c in row]
79
+ if conf['skip-empty-rows'] and not any(c is not None for c in row_values):
80
+ # skip empty rows
81
+ continue
82
+ if conf['rows'] == 'list':
83
+ result.append(row_values)
84
+ else:
85
+ result.append(dict(zip(headers, row_values)))
86
+
87
+ return result, metadata
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: ogc_na
3
- Version: 0.3.55
3
+ Version: 0.3.57
4
4
  Summary: OGC Naming Authority tools
5
5
  Author-email: Rob Atkinson <ratkinson@ogc.org>, Piotr Zaborowski <pzaborowski@ogc.org>, Alejandro Villar <avillar@ogc.org>
6
6
  Project-URL: Homepage, https://github.com/opengeospatial/ogc-na-tools/
@@ -27,6 +27,7 @@ Requires-Dist: rfc3987
27
27
  Requires-Dist: requests-cache
28
28
  Requires-Dist: xmltodict
29
29
  Requires-Dist: jsonpointer~=2.4
30
+ Requires-Dist: openpyxl~=3.1.5
30
31
  Requires-Dist: setuptools
31
32
  Provides-Extra: dev
32
33
  Requires-Dist: mkdocs>=1.4.2; extra == "dev"
@@ -28,6 +28,7 @@ ogc/na/util.py
28
28
  ogc/na/validation.py
29
29
  ogc/na/input_filters/__init__.py
30
30
  ogc/na/input_filters/csv.py
31
+ ogc/na/input_filters/xlsx.py
31
32
  ogc/na/input_filters/xml.py
32
33
  ogc_na.egg-info/PKG-INFO
33
34
  ogc_na.egg-info/SOURCES.txt
@@ -40,14 +41,19 @@ test/__init__.py
40
41
  test/test_annotate_schema.py
41
42
  test/test_ingest_json.py
42
43
  test/test_input_filters_csv.py
44
+ test/test_input_filters_xlsx.py
43
45
  test/test_profile.py
44
46
  test/data/empty.ttl
45
47
  test/data/headers.csv
48
+ test/data/headers.xlsx
46
49
  test/data/no-headers.csv
50
+ test/data/no-headers.xlsx
47
51
  test/data/profile_tree.ttl
48
52
  test/data/profile_tree_cyclic.ttl
49
53
  test/data/sample-context.jsonld
50
54
  test/data/sample-schema-prop-c.yml
51
55
  test/data/sample-schema.yml
56
+ test/data/schema-anchors.json
52
57
  test/data/schema-vocab.yml
58
+ test/data/two-sheets.xlsx
53
59
  test/data/uplift_context_valid.yml
@@ -12,6 +12,7 @@ rfc3987
12
12
  requests-cache
13
13
  xmltodict
14
14
  jsonpointer~=2.4
15
+ openpyxl~=3.1.5
15
16
  setuptools
16
17
 
17
18
  [dev]
@@ -12,6 +12,7 @@ rfc3987
12
12
  requests-cache
13
13
  xmltodict
14
14
  jsonpointer~=2.4
15
+ openpyxl~=3.1.5
15
16
 
16
17
  # to be removed once https://github.com/RDFLib/pySHACL/issues/212 is fixed
17
18
  setuptools
Binary file
Binary file
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "properties": {
4
+ "name": { "$ref": "https://example.com/person/name#name" },
5
+ "age": { "$ref": "https://example.com/person/age#age" }
6
+ },
7
+ "required": [ "name", "age" ],
8
+ "$defs": {
9
+ "name": {
10
+ "$id": "https://example.com/person/name",
11
+ "$anchor": "name",
12
+ "type": "string"
13
+ },
14
+ "age": {
15
+ "$id": "https://example.com/person/age",
16
+ "$anchor": "age",
17
+ "type": "integer"
18
+ },
19
+ "deep": {
20
+ "type": "object",
21
+ "properties": {
22
+ "inner": {
23
+ "$anchor": "innerProp",
24
+ "type": "string"
25
+ }
26
+ }
27
+ }
28
+ }
29
+ }
Binary file
@@ -1,8 +1,9 @@
1
+ import json
1
2
  import unittest
2
3
  from pathlib import Path
3
4
 
4
5
  from ogc.na import annotate_schema
5
- from ogc.na.annotate_schema import SchemaAnnotator, ContextBuilder
6
+ from ogc.na.annotate_schema import SchemaAnnotator, ContextBuilder, ReferencedSchema, SchemaResolver
6
7
 
7
8
  THIS_DIR = Path(__file__).parent
8
9
  DATA_DIR = THIS_DIR / 'data'
@@ -109,3 +110,14 @@ class AnnotateSchemaTest(unittest.TestCase):
109
110
 
110
111
  self.assertEqual(deep_get(builder.context, '@context', '@vocab'), vocab)
111
112
  self.assertEqual(deep_get(builder.context, '@context', '@base'), base)
113
+
114
+ def test_schema_anchors(self):
115
+ with open(DATA_DIR / 'schema-anchors.json') as f:
116
+ schema = json.load(f)
117
+ anchors = SchemaResolver._find_anchors(schema)
118
+ self.assertSetEqual({'name', 'age', 'innerProp'}, set(anchors.keys()))
119
+
120
+ self.assertEqual(SchemaResolver._get_branch(schema, '#/$defs/name'), anchors.get('name'))
121
+ self.assertEqual(SchemaResolver._get_branch(schema, '#/$defs/age'), anchors.get('age'))
122
+ self.assertEqual(SchemaResolver._get_branch(schema, '#/$defs/deep/properties/inner'),
123
+ anchors.get('innerProp'))
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import unittest
4
+ from io import BytesIO
5
+ from pathlib import Path
6
+
7
+ from ogc.na.input_filters import xlsx
8
+
9
+ THIS_DIR = Path(__file__).parent
10
+ DATA_DIR = THIS_DIR / 'data'
11
+
12
+ with open(DATA_DIR / 'headers.xlsx', 'rb') as f:
13
+ WITH_HEADERS = f.read()
14
+ with open(DATA_DIR / 'no-headers.xlsx', 'rb') as f:
15
+ NO_HEADERS = f.read()
16
+ with open(DATA_DIR / 'two-sheets.xlsx', 'rb') as f:
17
+ TWO_SHEETS = f.read()
18
+
19
+
20
+ class InputFiltersCSVTest(unittest.TestCase):
21
+
22
+ def test_rows_objects(self):
23
+ cfg = {
24
+ 'rows': 'dict',
25
+ 'skip-rows': 0,
26
+ 'header-row': 0,
27
+ }
28
+ rows = xlsx.apply_filter(WITH_HEADERS, cfg)[0]
29
+ self.assertEqual(len(rows), 87)
30
+ self.assertEqual(rows[0], {
31
+ 'Year': 1968,
32
+ 'Score': 86,
33
+ 'Title': 'Greetings',
34
+ })
35
+ self.assertEqual(rows[10], {
36
+ 'Year': 1977,
37
+ 'Score': 67,
38
+ 'Title': 'New York,New York',
39
+ })
40
+
41
+ cfg['skip-rows'] = 3
42
+ rows = xlsx.apply_filter(WITH_HEADERS, cfg)[0]
43
+ self.assertEqual(len(rows), 84)
44
+ self.assertEqual(rows[0], {
45
+ 'Year': 1971,
46
+ 'Score': 40,
47
+ 'Title': 'Born to Win',
48
+ })
49
+ self.assertEqual(rows[7], {
50
+ 'Year': 1977,
51
+ 'Score': 67,
52
+ 'Title': 'New York,New York',
53
+ })
54
+
55
+ cfg['skip-rows'] = 0
56
+ cfg['header-row'] = 2
57
+ rows = xlsx.apply_filter(WITH_HEADERS, cfg)[0]
58
+ self.assertEqual(len(rows), 85)
59
+ self.assertEqual(rows[0], {
60
+ 1970: 1970,
61
+ 17: 73,
62
+ 'Bloody Mama': 'Hi,Mom!',
63
+ })
64
+
65
+ def test_rows_lists(self):
66
+ cfg = {
67
+ 'rows': 'list',
68
+ 'skip-rows': 0,
69
+ }
70
+ rows = xlsx.apply_filter(WITH_HEADERS, cfg)[0]
71
+ self.assertEqual(len(rows), 88)
72
+ self.assertEqual(rows[0], ['Year', 'Score', 'Title'])
73
+ self.assertEqual(rows[10], [1977, 47, '1900'])
74
+ cfg['skip-rows'] = 1
75
+ rows = xlsx.apply_filter(WITH_HEADERS, cfg)[0]
76
+ self.assertEqual(len(rows), 87)
77
+ self.assertEqual(rows[0], [1968, 86, "Greetings"])
78
+ self.assertEqual(rows[3], [1971, 40, "Born to Win"])
79
+ cfg['header-row'] = 2 # should have no effect
80
+ self.assertEqual(rows, xlsx.apply_filter(WITH_HEADERS, cfg)[0])
81
+
82
+ cfg['skip-rows'] = 0
83
+ rows = xlsx.apply_filter(NO_HEADERS, cfg)[0]
84
+ self.assertEqual(len(rows), 6)
85
+ self.assertEqual(rows[0], ['John', 'Doe', '120 jefferson st.', 'Riverside', ' NJ', ' 08075'])
86
+
87
+ def test_several_sheets(self):
88
+ rows, metadata = xlsx.apply_filter(TWO_SHEETS, {})
89
+ self.assertEqual(metadata.get('worksheet'), 'FirstSheet')
90
+ self.assertEqual(rows[0], {'FirstCol1': 1, 'FirstCol2': 'DD37Cf93aecA6Dc'})
91
+
92
+ rows, metadata = xlsx.apply_filter(TWO_SHEETS, {'worksheet': 'SecondSheet'})
93
+ self.assertEqual(metadata.get('worksheet'), 'SecondSheet')
94
+ self.assertEqual(rows[0], {'SecondCol1': 11, 'SecondCol2': '216E205d6eBb815'})
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes