dsff 1.2.0__tar.gz → 1.2.3__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.
- {dsff-1.2.0/src/dsff.egg-info → dsff-1.2.3}/PKG-INFO +31 -4
- {dsff-1.2.0 → dsff-1.2.3}/README.md +26 -1
- dsff-1.2.3/docs/coverage.svg +1 -0
- {dsff-1.2.0 → dsff-1.2.3}/pyproject.toml +2 -3
- dsff-1.2.3/src/dsff/VERSION.txt +1 -0
- dsff-1.2.3/src/dsff/formats/__common__.py +56 -0
- {dsff-1.2.0 → dsff-1.2.3}/src/dsff/formats/__init__.py +15 -0
- {dsff-1.2.0 → dsff-1.2.3}/src/dsff/formats/arff.py +84 -61
- dsff-1.2.3/src/dsff/formats/csv.py +41 -0
- dsff-1.2.3/src/dsff/formats/dataset.py +67 -0
- {dsff-1.2.0 → dsff-1.2.3}/src/dsff/formats/db.py +44 -7
- dsff-1.2.3/src/dsff/formats/pa.py +46 -0
- {dsff-1.2.0 → dsff-1.2.3/src/dsff.egg-info}/PKG-INFO +31 -4
- dsff-1.2.3/src/dsff.egg-info/requires.txt +7 -0
- {dsff-1.2.0 → dsff-1.2.3}/tests/test_dsff.py +35 -13
- dsff-1.2.0/docs/coverage.svg +0 -1
- dsff-1.2.0/src/dsff/VERSION.txt +0 -1
- dsff-1.2.0/src/dsff/formats/__common__.py +0 -33
- dsff-1.2.0/src/dsff/formats/csv.py +0 -29
- dsff-1.2.0/src/dsff/formats/dataset.py +0 -40
- dsff-1.2.0/src/dsff/formats/pa.py +0 -29
- dsff-1.2.0/src/dsff.egg-info/requires.txt +0 -4
- {dsff-1.2.0 → dsff-1.2.3}/.coveragerc +0 -0
- {dsff-1.2.0 → dsff-1.2.3}/.github/workflows/python-package.yml +0 -0
- {dsff-1.2.0 → dsff-1.2.3}/.gitignore +0 -0
- {dsff-1.2.0 → dsff-1.2.3}/.readthedocs.yml +0 -0
- {dsff-1.2.0 → dsff-1.2.3}/LICENSE +0 -0
- {dsff-1.2.0 → dsff-1.2.3}/docs/mkdocs.yml +0 -0
- {dsff-1.2.0 → dsff-1.2.3}/docs/pages/imgs/dsff-from.png +0 -0
- {dsff-1.2.0 → dsff-1.2.3}/docs/pages/imgs/dsff-to.png +0 -0
- {dsff-1.2.0 → dsff-1.2.3}/docs/pages/imgs/icon.png +0 -0
- {dsff-1.2.0 → dsff-1.2.3}/docs/pages/imgs/logo.png +0 -0
- {dsff-1.2.0 → dsff-1.2.3}/docs/pages/index.md +0 -0
- {dsff-1.2.0 → dsff-1.2.3}/docs/pages/usage.md +0 -0
- {dsff-1.2.0 → dsff-1.2.3}/docs/requirements.txt +0 -0
- {dsff-1.2.0 → dsff-1.2.3}/pytest.ini +0 -0
- {dsff-1.2.0 → dsff-1.2.3}/requirements.txt +0 -0
- {dsff-1.2.0 → dsff-1.2.3}/setup.cfg +0 -0
- {dsff-1.2.0 → dsff-1.2.3}/src/dsff/__info__.py +0 -0
- {dsff-1.2.0 → dsff-1.2.3}/src/dsff/__init__.py +0 -0
- {dsff-1.2.0 → dsff-1.2.3}/src/dsff.egg-info/SOURCES.txt +0 -0
- {dsff-1.2.0 → dsff-1.2.3}/src/dsff.egg-info/dependency_links.txt +0 -0
- {dsff-1.2.0 → dsff-1.2.3}/src/dsff.egg-info/top_level.txt +0 -0
- {dsff-1.2.0 → dsff-1.2.3}/tests/__init__.py +0 -0
- {dsff-1.2.0 → dsff-1.2.3}/tests/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dsff
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.3
|
|
4
4
|
Summary: DataSet File Format (DSFF)
|
|
5
5
|
Author-email: Alexandre D'Hondt <alexandre.dhondt@gmail.com>
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -696,8 +696,10 @@ Requires-Python: <4,>=3.10
|
|
|
696
696
|
Description-Content-Type: text/markdown
|
|
697
697
|
License-File: LICENSE
|
|
698
698
|
Requires-Dist: openpyxl
|
|
699
|
-
Provides-Extra:
|
|
700
|
-
Requires-Dist:
|
|
699
|
+
Provides-Extra: all
|
|
700
|
+
Requires-Dist: dsff[arrow]; extra == "all"
|
|
701
|
+
Provides-Extra: arrow
|
|
702
|
+
Requires-Dist: pyarrow; extra == "arrow"
|
|
701
703
|
Dynamic: license-file
|
|
702
704
|
|
|
703
705
|
<p align="center" id="top"><img src="https://github.com/packing-box/python-dsff/raw/main/docs/pages/imgs/logo.png"></p>
|
|
@@ -738,9 +740,34 @@ pip install --user dsff
|
|
|
738
740
|
```python
|
|
739
741
|
>>> import dsff
|
|
740
742
|
>>> with dsff.DSFF("/path/to/my-dataset.dsff") as f:
|
|
741
|
-
f.to_dataset() # creates ./
|
|
743
|
+
f.to_dataset() # creates ./my-dataset with data.csv, features.json and metadata.json
|
|
742
744
|
```
|
|
743
745
|
|
|
746
|
+
**
|
|
747
|
+
|
|
748
|
+
## Extensions
|
|
749
|
+
|
|
750
|
+
**Install all available extensions**
|
|
751
|
+
|
|
752
|
+
```sh
|
|
753
|
+
pip install --user dsff[all]
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
**Dealing with [Apache Arrow](https://arrow.apache.org/) formats**
|
|
757
|
+
|
|
758
|
+
```sh
|
|
759
|
+
pip install --user dsff[arrow]
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
```python
|
|
763
|
+
>>> import dsff
|
|
764
|
+
>>> with dsff.DSFF("/path/to/my-dataset.dsff") as f:
|
|
765
|
+
f.to_feather() # creates ./my-dataset.feather
|
|
766
|
+
f.to_orc() # creates ./my-dataset.orc
|
|
767
|
+
f.to_parquet() # creates ./my-dataset.parquet
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
|
|
744
771
|
## Related Projects
|
|
745
772
|
|
|
746
773
|
You may also like these:
|
|
@@ -36,9 +36,34 @@ pip install --user dsff
|
|
|
36
36
|
```python
|
|
37
37
|
>>> import dsff
|
|
38
38
|
>>> with dsff.DSFF("/path/to/my-dataset.dsff") as f:
|
|
39
|
-
f.to_dataset() # creates ./
|
|
39
|
+
f.to_dataset() # creates ./my-dataset with data.csv, features.json and metadata.json
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
**
|
|
43
|
+
|
|
44
|
+
## Extensions
|
|
45
|
+
|
|
46
|
+
**Install all available extensions**
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
pip install --user dsff[all]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Dealing with [Apache Arrow](https://arrow.apache.org/) formats**
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
pip install --user dsff[arrow]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
>>> import dsff
|
|
60
|
+
>>> with dsff.DSFF("/path/to/my-dataset.dsff") as f:
|
|
61
|
+
f.to_feather() # creates ./my-dataset.feather
|
|
62
|
+
f.to_orc() # creates ./my-dataset.orc
|
|
63
|
+
f.to_parquet() # creates ./my-dataset.parquet
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
|
|
42
67
|
## Related Projects
|
|
43
68
|
|
|
44
69
|
You may also like these:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="114" height="20" role="img" aria-label="coverage: 93.24%"><title>coverage: 93.24%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="114" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="53" height="20" fill="#4c1"/><rect width="114" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="315" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">coverage</text><text x="315" y="140" transform="scale(.1)" fill="#fff" textLength="510">coverage</text><text aria-hidden="true" x="865" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">93.24%</text><text x="865" y="140" transform="scale(.1)" fill="#fff" textLength="430">93.24%</text></g></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.2.3
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
import csv as csvmod
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
from ast import literal_eval
|
|
6
|
+
from io import BytesIO, StringIO
|
|
7
|
+
from os import makedirs, remove
|
|
8
|
+
from os.path import basename, exists, expanduser, isfile, isdir, join, splitext
|
|
9
|
+
try: # pragma: no cover
|
|
10
|
+
import pyarrow
|
|
11
|
+
import pyarrow.feather as feather
|
|
12
|
+
import pyarrow.orc as orc
|
|
13
|
+
import pyarrow.parquet as parquet
|
|
14
|
+
import pandas
|
|
15
|
+
PYARROW = True
|
|
16
|
+
except ImportError: # pragma: no cover
|
|
17
|
+
PYARROW = False
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = ["basename", "csvmod", "ensure_str", "exists", "expanduser", "isfile", "isdir", "join", "json",
|
|
21
|
+
"literal_eval", "makedirs", "re", "remove", "splitext", "text_or_path", "BytesIO", "StringIO",
|
|
22
|
+
"CSV_DELIMITER", "DEFAULT_EXCL", "INMEMORY", "META_EXCL", "MISSING_TOKEN", "PYARROW", "TARGET_NAME"]
|
|
23
|
+
if PYARROW:
|
|
24
|
+
__all__ += ["feather", "orc", "pandas", "parquet", "pyarrow"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
CSV_DELIMITER = ";"
|
|
28
|
+
DEFAULT_EXCL = ("hash", "realpath", "format", "size", "ctime", "mtime") # origin: executables used in the Packing Box
|
|
29
|
+
INMEMORY = "<memory>"
|
|
30
|
+
META_EXCL = ["created", "modified", "revision"]
|
|
31
|
+
MISSING_TOKEN = "?"
|
|
32
|
+
TARGET_NAME = "label"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def ensure_str(string):
|
|
36
|
+
try:
|
|
37
|
+
return string.decode()
|
|
38
|
+
except AttributeError:
|
|
39
|
+
return str(string)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def text_or_path(f):
|
|
43
|
+
def _wrapper(txt_path, *args, **kwargs):
|
|
44
|
+
if not isinstance(txt_path, bytes):
|
|
45
|
+
if isinstance(txt_path, str):
|
|
46
|
+
if not re.search(r"[<>:\'\"/\\|?*\x00-\x1f]", txt_path, re.I) and exists(txt_path):
|
|
47
|
+
if not isdir(txt_path):
|
|
48
|
+
with open(txt_path, "rb") as fh:
|
|
49
|
+
txt_path = fh.read()
|
|
50
|
+
else:
|
|
51
|
+
txt_path = txt_path.encode()
|
|
52
|
+
else:
|
|
53
|
+
raise ValueError(f"Bad input text or path ({type(txt_path)})")
|
|
54
|
+
return f(txt_path, *args, **kwargs)
|
|
55
|
+
return _wrapper
|
|
56
|
+
|
|
@@ -22,6 +22,9 @@ if PYARROW: # pragma: no cover
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
__all__ = ["DSFF"]
|
|
25
|
+
for k in list(globals().keys()):
|
|
26
|
+
if k.startswith("is_") or k.startswith("load_"):
|
|
27
|
+
__all__.append(k)
|
|
25
28
|
|
|
26
29
|
_FORMAT_TEXT_ALIAS = {'arff': "ARFF", 'csv': "CSV", 'db': "SQL", 'orc': "ORC"}
|
|
27
30
|
_NO_EXT_FORMAT = ["dataset"]
|
|
@@ -343,4 +346,16 @@ class DSFF:
|
|
|
343
346
|
if p != INMEMORY and not p.endswith(".dsff"):
|
|
344
347
|
p += ".dsff"
|
|
345
348
|
return p
|
|
349
|
+
|
|
350
|
+
@staticmethod
|
|
351
|
+
@text_or_path
|
|
352
|
+
def format(text):
|
|
353
|
+
for k, func in globals().items():
|
|
354
|
+
if k.startswith("is_") and func(text):
|
|
355
|
+
return k.split("_", 1)[1]
|
|
356
|
+
|
|
357
|
+
@staticmethod
|
|
358
|
+
@text_or_path
|
|
359
|
+
def is_valid_format(text):
|
|
360
|
+
return DSFF.format(text) is not None
|
|
346
361
|
|
|
@@ -2,68 +2,67 @@
|
|
|
2
2
|
from .__common__ import *
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
__all__ = ["from_arff", "to_arff"]
|
|
5
|
+
__all__ = ["from_arff", "is_arff", "load_arff", "to_arff"]
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
def
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if l.startswith("@RELATION "):
|
|
18
|
-
relation = True
|
|
19
|
-
try:
|
|
20
|
-
dsff['title'] = re.match(r"@RELATION\s+('[^']*'|\"[^\"]*\")$", l).group(1).strip("'\"")
|
|
21
|
-
continue
|
|
22
|
-
except Exception as e:
|
|
23
|
-
raise BadInputData(f"{pf}failed on @RELATION ({e})")
|
|
24
|
-
else:
|
|
25
|
-
raise BadInputData(f"{pf}did not find @RELATION")
|
|
26
|
-
# get metadata and feature descriptions from comments
|
|
27
|
-
if l.startswith("%"):
|
|
28
|
-
if re.match(r"^\%\s+metadata\s*\:\s*\{.*\}$", l):
|
|
29
|
-
dsff.write(metadata=literal_eval(l.split(":", 1)[1]))
|
|
30
|
-
elif (m := re.match(r"^\%\s+(.*?)\s*\:\s*(.*?)$", l)):
|
|
31
|
-
name, descr = m.groups()
|
|
32
|
-
features[name] = descr
|
|
33
|
-
continue
|
|
34
|
-
# then ignore blank lines
|
|
35
|
-
if l == "":
|
|
36
|
-
if attributes[0] and not attributes[1]:
|
|
37
|
-
# close the atributes block
|
|
38
|
-
attributes[1] = True
|
|
39
|
-
n_cols = len(d[0])
|
|
40
|
-
continue
|
|
41
|
-
if l.startswith("@ATTRIBUTE "):
|
|
42
|
-
if not attributes[0]:
|
|
43
|
-
attributes[0] = True
|
|
44
|
-
if len(d) == 0:
|
|
45
|
-
# start the attributes block
|
|
46
|
-
d.append([])
|
|
47
|
-
if attributes[1]:
|
|
48
|
-
raise BadInputData(f"{pf}found @ATTRIBUTE out of the attributes block)")
|
|
8
|
+
def _parse(text_or_fh, target=TARGET_NAME, missing=MISSING_TOKEN, **kw):
|
|
9
|
+
d, features, metadata, title = [], {}, {}, ""
|
|
10
|
+
relation, attributes, data = False, [False, False], False
|
|
11
|
+
for n, l in enumerate(t.splitlines() if isinstance(t := text_or_fh, str) else t):
|
|
12
|
+
l, pf = l.strip(), f"Line {n}: "
|
|
13
|
+
# the file shall start with "@RELATION"
|
|
14
|
+
if not relation:
|
|
15
|
+
if l.upper().startswith("@RELATION "):
|
|
16
|
+
relation = True
|
|
49
17
|
try:
|
|
50
|
-
|
|
51
|
-
if header == "class":
|
|
52
|
-
header = target
|
|
53
|
-
d[0].append(header)
|
|
54
|
-
continue
|
|
55
|
-
except AttributeError:
|
|
56
|
-
raise BadInputData(f"{pf}failed on @ATTRIBUTE (bad type)")
|
|
57
|
-
if not data:
|
|
58
|
-
if l == "@DATA":
|
|
59
|
-
data = True
|
|
18
|
+
title = re.match(r"@RELATION\s+('[^']*'|\"[^\"]*\")$", l, re.I).group(1).strip("'\"")
|
|
60
19
|
continue
|
|
20
|
+
except Exception as e:
|
|
21
|
+
raise BadInputData(f"{pf}failed on @RELATION ({e})")
|
|
22
|
+
else:
|
|
23
|
+
raise BadInputData(f"{pf}did not find @RELATION")
|
|
24
|
+
# get metadata and feature descriptions from comments
|
|
25
|
+
if l.startswith("%"):
|
|
26
|
+
if re.match(r"^\%\s+metadata\s*\:\s*\{.*\}$", l):
|
|
27
|
+
metadata = literal_eval(l.split(":", 1)[1])
|
|
28
|
+
elif (m := re.match(r"^\%\s+(.*?)\s*\:\s*(.*?)$", l)):
|
|
29
|
+
name, descr = m.groups()
|
|
30
|
+
features[name] = descr
|
|
31
|
+
continue
|
|
32
|
+
# then ignore blank lines
|
|
33
|
+
if l == "":
|
|
34
|
+
if attributes[0] and not attributes[1]:
|
|
35
|
+
# close the atributes block
|
|
36
|
+
attributes[1] = True
|
|
37
|
+
continue
|
|
38
|
+
if l.upper().startswith("@ATTRIBUTE "):
|
|
39
|
+
if not attributes[0]:
|
|
40
|
+
attributes[0] = True
|
|
41
|
+
if len(d) == 0:
|
|
42
|
+
# start the attributes block
|
|
43
|
+
d.append([])
|
|
44
|
+
if attributes[1]:
|
|
45
|
+
raise BadInputData(f"{pf}found @ATTRIBUTE out of the attributes block)")
|
|
46
|
+
try:
|
|
47
|
+
header = re.match(r"@ATTRIBUTE\s+([^\s]+)\s+(?:[a-zA-Z]+|\{.*?\})$", l, re.I).group(1).strip("'\"")
|
|
48
|
+
if header == "class":
|
|
49
|
+
header = target
|
|
61
50
|
else:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
51
|
+
features.setdefault(header, "")
|
|
52
|
+
d[0].append(header)
|
|
53
|
+
continue
|
|
54
|
+
except AttributeError:
|
|
55
|
+
raise BadInputData(f"{pf}failed on @ATTRIBUTE (bad type)")
|
|
56
|
+
if not data:
|
|
57
|
+
if l.upper() == "@DATA":
|
|
58
|
+
data = True
|
|
59
|
+
n_cols = len(d[0])
|
|
60
|
+
continue
|
|
61
|
+
else:
|
|
62
|
+
raise BadInputData(f"{pf}did not find @DATA where expected")
|
|
63
|
+
if len(row := list(map(lambda x: x.strip("'\""), re.split(r",\s*", l)))) != n_cols:
|
|
64
|
+
raise BadInputData(f"{pf}this row does not match the number of columns")
|
|
65
|
+
d.append(row)
|
|
67
66
|
for i in range(n_cols):
|
|
68
67
|
values = []
|
|
69
68
|
for j, row in enumerate(d):
|
|
@@ -76,12 +75,36 @@ def from_arff(dsff, path=None, target=TARGET_NAME, missing=MISSING_TOKEN):
|
|
|
76
75
|
if "".join(sorted(set(values))) in ["0", "1", "01"]:
|
|
77
76
|
for j, row in enumerate(d):
|
|
78
77
|
if j > 0:
|
|
79
|
-
row[i] = {'0': "False", '1': "True"}[row[i]]
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
row[i] = {'0': "False", '1': "True"}[row[i]]
|
|
79
|
+
return d, features, metadata, title
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def from_arff(dsff, path=None, target=TARGET_NAME, missing=MISSING_TOKEN, **kw):
|
|
83
|
+
""" Populate the DSFF file from an ARFF file. """
|
|
84
|
+
with open(path) as f:
|
|
85
|
+
d, ft, md, t = _parse(f, target, missing)
|
|
86
|
+
dsff['title'] = t
|
|
87
|
+
dsff.write(data=d, features=ft, metadata=md)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@text_or_path
|
|
91
|
+
def is_arff(text, target=TARGET_NAME, missing=MISSING_TOKEN, **kw):
|
|
92
|
+
""" Check if the input text or path is a valid ARFF. """
|
|
93
|
+
try:
|
|
94
|
+
_parse(ensure_str(text))
|
|
95
|
+
return True
|
|
96
|
+
except (BadInputData, UnicodeDecodeError):
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def load_arff(path, target=TARGET_NAME, missing=MISSING_TOKEN, **kw):
|
|
101
|
+
""" Load an ARFF file as a dictionary with data, features and metadata. """
|
|
102
|
+
with open(path) as f:
|
|
103
|
+
d, ft, md, _ = _parse(f, target, missing)
|
|
104
|
+
return {'data': d, 'features': ft, 'metadata': md}
|
|
82
105
|
|
|
83
106
|
|
|
84
|
-
def to_arff(dsff, path=None, target=TARGET_NAME, exclude=DEFAULT_EXCL, missing=MISSING_TOKEN, text=False):
|
|
107
|
+
def to_arff(dsff, path=None, target=TARGET_NAME, exclude=DEFAULT_EXCL, missing=MISSING_TOKEN, text=False, **kw):
|
|
85
108
|
""" Output the dataset in ARFF format, suitable for use with the Weka framework, saved as a file or output as a
|
|
86
109
|
string. """
|
|
87
110
|
name = splitext(basename(path))[0]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
from .__common__ import *
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
__all__ = ["from_csv", "is_csv", "load_csv", "to_csv"]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def from_csv(dsff, path=None, exclude=DEFAULT_EXCL, **kw):
|
|
9
|
+
""" Populate the DSFF file from a CSV file. """
|
|
10
|
+
dsff.write(**load_csv(path))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@text_or_path
|
|
14
|
+
def is_csv(text, **kw):
|
|
15
|
+
""" Check if the input text or path is a valid CSV. """
|
|
16
|
+
try:
|
|
17
|
+
dialect = csvmod.Sniffer().sniff(text := ensure_str(text))
|
|
18
|
+
csvmod.Sniffer().has_header(text)
|
|
19
|
+
return True
|
|
20
|
+
except (csvmod.Error, UnicodeDecodeError):
|
|
21
|
+
return False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def load_csv(path, exclude=DEFAULT_EXCL, **kw):
|
|
25
|
+
""" Load a CSV file as a dictionary with data, features and metadata. """
|
|
26
|
+
data = {'metadata': {}}
|
|
27
|
+
with open(expanduser(path)) as f:
|
|
28
|
+
data['data'] = [r for r in csvmod.reader(f, delimiter=CSV_DELIMITER)]
|
|
29
|
+
data['features'] = {h: "" for h in data['data'][0] if h not in exclude}
|
|
30
|
+
return data
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def to_csv(dsff, path=None, text=False, **kw):
|
|
34
|
+
""" Create a CSV from the data worksheet, saved as a file or output as a string. """
|
|
35
|
+
with (StringIO() if text else open(path, 'w+')) as f:
|
|
36
|
+
writer = csvmod.writer(f, delimiter=";")
|
|
37
|
+
for row in dsff.data:
|
|
38
|
+
writer.writerow(row)
|
|
39
|
+
if text:
|
|
40
|
+
return f.getvalue()
|
|
41
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
from .__common__ import *
|
|
3
|
+
from .csv import load_csv
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
__all__ = ["from_dataset", "is_dataset", "load_dataset", "to_dataset"]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _parse(path):
|
|
10
|
+
if not isdir(expanduser(path)):
|
|
11
|
+
raise BadInputData("Not a folder")
|
|
12
|
+
if len(missing := [f for f in ["data.csv", "features.json", "metadata.json"] if not isfile(join(path, f))]) > 0:
|
|
13
|
+
raise BadInputData(f"Not a valid dataset folder (missing: {', '.join(missing)})")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def from_dataset(dsff, path=None, **kw):
|
|
17
|
+
""" Populate the DSFF file from a Dataset structure. """
|
|
18
|
+
_parse(path)
|
|
19
|
+
dsff.write(path)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def is_dataset(path, **kw):
|
|
23
|
+
""" Check if the input path is a valid Dataset. """
|
|
24
|
+
try:
|
|
25
|
+
_parse(path)
|
|
26
|
+
return True
|
|
27
|
+
except BadInputData:
|
|
28
|
+
return False
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def load_dataset(path, **kw):
|
|
32
|
+
""" Load a dataset folder as a dictionary with data, features and metadata. """
|
|
33
|
+
if not isdir(d := expanduser(str(path))):
|
|
34
|
+
raise BadInputData("Not a folder")
|
|
35
|
+
dp, fp, mp = join(d, "data.csv"), join(d, "features.json"), join(d, "metadata.json")
|
|
36
|
+
data = {}
|
|
37
|
+
data['data'] = load_csv(dp)['data']
|
|
38
|
+
with open(fp) as f:
|
|
39
|
+
data['features'] = json.load(f)
|
|
40
|
+
with open(mp) as f:
|
|
41
|
+
data['metadata'] = json.load(f)
|
|
42
|
+
return data
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def to_dataset(dsff, path=None, **kw):
|
|
46
|
+
""" Create a dataset folder according to the following structure ;
|
|
47
|
+
name
|
|
48
|
+
+-- data.csv
|
|
49
|
+
+-- features.json
|
|
50
|
+
+-- metadata.json
|
|
51
|
+
"""
|
|
52
|
+
makedirs(path, exist_ok=True)
|
|
53
|
+
# handle data first
|
|
54
|
+
dsff.logger.debug("> making data.csv...")
|
|
55
|
+
with open(join(path, "data.csv"), 'w+') as f:
|
|
56
|
+
writer = csvmod.writer(f, delimiter=";")
|
|
57
|
+
for cells in dsff['data'].rows:
|
|
58
|
+
writer.writerow([dsff._DSFF__eval(c.value) for c in cells])
|
|
59
|
+
# then handle features dictionary
|
|
60
|
+
dsff.logger.debug("> making features.json...")
|
|
61
|
+
with open(join(path, "features.json"), 'w+') as f:
|
|
62
|
+
json.dump(dsff.features, f, indent=2)
|
|
63
|
+
# finally handle metadata dictionary
|
|
64
|
+
dsff.logger.debug("> making metadata.json...")
|
|
65
|
+
with open(join(path, "metadata.json"), 'w+') as f:
|
|
66
|
+
json.dump(dsff.metadata, f, indent=2)
|
|
67
|
+
|
|
@@ -2,15 +2,51 @@
|
|
|
2
2
|
from .__common__ import *
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
__all__ = ["from_db", "to_db"]
|
|
5
|
+
__all__ = ["from_db", "is_db", "load_db", "to_db"]
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
def from_db(dsff, path=None,
|
|
8
|
+
def from_db(dsff, path=None, **kw):
|
|
9
9
|
""" Populate the DSFF file from a SQLDB file. """
|
|
10
|
+
dsff.write(**load_db(path))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@text_or_path
|
|
14
|
+
def is_db(data, **kw):
|
|
15
|
+
""" Check if the input data or path is a valid SQL database. """
|
|
16
|
+
from sqlite3 import connect, Error
|
|
17
|
+
from sys import version_info
|
|
18
|
+
if not data.startswith(b"SQLite format 3\x00"):
|
|
19
|
+
return False
|
|
20
|
+
if version_info.minor > 10:
|
|
21
|
+
try:
|
|
22
|
+
with connect(":memory:") as c:
|
|
23
|
+
c.deserialize(data)
|
|
24
|
+
c.execute("PRAGMA schema_version;")
|
|
25
|
+
return True
|
|
26
|
+
except Error:
|
|
27
|
+
return False
|
|
28
|
+
else: # pragma: no cover
|
|
29
|
+
from os import close, write
|
|
30
|
+
from tempfile import mkstemp
|
|
31
|
+
fd, path = mkstemp(suffix=".db")
|
|
32
|
+
try:
|
|
33
|
+
write(fd, data)
|
|
34
|
+
close(fd)
|
|
35
|
+
fd = None
|
|
36
|
+
with connect(f"file:{path}?mode=ro&immutable=1", uri=True) as c:
|
|
37
|
+
c.execute("PRAGMA schema_version;").fetchone()
|
|
38
|
+
return True
|
|
39
|
+
except (Error, OSError):
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def load_db(path, **kw):
|
|
44
|
+
""" Load a SQLDB file as a dictionary with data, features and metadata. """
|
|
10
45
|
from json import loads
|
|
46
|
+
from os.path import basename, splitext
|
|
11
47
|
from sqlite3 import connect
|
|
12
48
|
conn = connect(path)
|
|
13
|
-
cursor = conn.cursor()
|
|
49
|
+
cursor, data = conn.cursor(), {}
|
|
14
50
|
# list tables
|
|
15
51
|
cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
|
|
16
52
|
tables = [table[0] for table in cursor.fetchall()]
|
|
@@ -20,17 +56,18 @@ def from_db(dsff, path=None, exclude=DEFAULT_EXCL):
|
|
|
20
56
|
cursor.execute("PRAGMA table_info('data')")
|
|
21
57
|
headers = [[col[1] for col in cursor.fetchall()]]
|
|
22
58
|
cursor.execute("SELECT * FROM data;")
|
|
23
|
-
|
|
59
|
+
data['data'] = headers + [r for r in cursor.fetchall()]
|
|
24
60
|
# import feature definitions
|
|
25
61
|
cursor.execute("SELECT name,description FROM features;")
|
|
26
|
-
|
|
62
|
+
data['features'] = {r[0]: r[1] for r in cursor.fetchall()}
|
|
27
63
|
# import metadata
|
|
28
64
|
cursor.execute("SELECT key,value FROM metadata;")
|
|
29
|
-
|
|
65
|
+
data['metadata'] = {r[0]: loads(r[1]) if isinstance(r[1], str) else r[1] for r in cursor.fetchall()}
|
|
30
66
|
conn.close()
|
|
67
|
+
return data
|
|
31
68
|
|
|
32
69
|
|
|
33
|
-
def to_db(dsff, path=None, text=False, primary_index=0):
|
|
70
|
+
def to_db(dsff, path=None, text=False, primary_index=0, **kw):
|
|
34
71
|
""" Create a SQLDB from the data worksheet, saved as a file or output as a string. """
|
|
35
72
|
from json import dumps
|
|
36
73
|
from sqlite3 import connect
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
from .__common__ import *
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
__all__ = []
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _nowrite(m):
|
|
9
|
+
raise NotImplementedError(f"none of {m}.write_table and {m}.write_{m} is implemented")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _parse(ds):
|
|
13
|
+
return {
|
|
14
|
+
'data': [ds.schema.names] + [list(r.values()) for r in ds.to_pylist()],
|
|
15
|
+
'features': {k.decode(): v.decode() for k, v in ds.schema.metadata.items()},
|
|
16
|
+
'metadata': literal_eval(ds.schema.metadata.pop(b'__metadata__', b"{}").decode()),
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
for module in ["feather", "orc", "parquet"]:
|
|
21
|
+
__all__ += [f"from_{module}", f"is_{module}", f"load_{module}", f"to_{module}"]
|
|
22
|
+
def gen_func(m):
|
|
23
|
+
def from_(dsff, path=None, **kw):
|
|
24
|
+
from os.path import basename, splitext
|
|
25
|
+
dsff.write(**_parse(globals()[m].read_table(path)))
|
|
26
|
+
from_.__name__ = f"from_{m}"
|
|
27
|
+
def is_(data, **kw):
|
|
28
|
+
try:
|
|
29
|
+
globals()[m].read_table(pyarrow.BufferReader(data))
|
|
30
|
+
return True
|
|
31
|
+
except Exception:
|
|
32
|
+
return False
|
|
33
|
+
is_.__name__ = f"is_{m}"
|
|
34
|
+
def load_(path, **kw):
|
|
35
|
+
return _parse(globals()[m].read_table(path))
|
|
36
|
+
load_.__name__ = f"load_{m}"
|
|
37
|
+
def to_(dsff, path=None, text=False, **kw):
|
|
38
|
+
with (BytesIO() if text else open(path, 'wb+')) as f:
|
|
39
|
+
getattr(globals()[m], "write_table", getattr(globals()[m], f"write_{m}", _nowrite))(dsff._to_table(), f)
|
|
40
|
+
if text:
|
|
41
|
+
return f.getvalue()
|
|
42
|
+
to_.__name__ = f"to_{m}"
|
|
43
|
+
return from_, text_or_path(is_), load_, to_
|
|
44
|
+
globals()[f'from_{module}'], globals()[f'is_{module}'], globals()[f'load_{module}'], globals()[f'to_{module}'] = \
|
|
45
|
+
gen_func(module)
|
|
46
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dsff
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.3
|
|
4
4
|
Summary: DataSet File Format (DSFF)
|
|
5
5
|
Author-email: Alexandre D'Hondt <alexandre.dhondt@gmail.com>
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -696,8 +696,10 @@ Requires-Python: <4,>=3.10
|
|
|
696
696
|
Description-Content-Type: text/markdown
|
|
697
697
|
License-File: LICENSE
|
|
698
698
|
Requires-Dist: openpyxl
|
|
699
|
-
Provides-Extra:
|
|
700
|
-
Requires-Dist:
|
|
699
|
+
Provides-Extra: all
|
|
700
|
+
Requires-Dist: dsff[arrow]; extra == "all"
|
|
701
|
+
Provides-Extra: arrow
|
|
702
|
+
Requires-Dist: pyarrow; extra == "arrow"
|
|
701
703
|
Dynamic: license-file
|
|
702
704
|
|
|
703
705
|
<p align="center" id="top"><img src="https://github.com/packing-box/python-dsff/raw/main/docs/pages/imgs/logo.png"></p>
|
|
@@ -738,9 +740,34 @@ pip install --user dsff
|
|
|
738
740
|
```python
|
|
739
741
|
>>> import dsff
|
|
740
742
|
>>> with dsff.DSFF("/path/to/my-dataset.dsff") as f:
|
|
741
|
-
f.to_dataset() # creates ./
|
|
743
|
+
f.to_dataset() # creates ./my-dataset with data.csv, features.json and metadata.json
|
|
742
744
|
```
|
|
743
745
|
|
|
746
|
+
**
|
|
747
|
+
|
|
748
|
+
## Extensions
|
|
749
|
+
|
|
750
|
+
**Install all available extensions**
|
|
751
|
+
|
|
752
|
+
```sh
|
|
753
|
+
pip install --user dsff[all]
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
**Dealing with [Apache Arrow](https://arrow.apache.org/) formats**
|
|
757
|
+
|
|
758
|
+
```sh
|
|
759
|
+
pip install --user dsff[arrow]
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
```python
|
|
763
|
+
>>> import dsff
|
|
764
|
+
>>> with dsff.DSFF("/path/to/my-dataset.dsff") as f:
|
|
765
|
+
f.to_feather() # creates ./my-dataset.feather
|
|
766
|
+
f.to_orc() # creates ./my-dataset.orc
|
|
767
|
+
f.to_parquet() # creates ./my-dataset.parquet
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
|
|
744
771
|
## Related Projects
|
|
745
772
|
|
|
746
773
|
You may also like these:
|
|
@@ -76,6 +76,7 @@ class TestDsff(TestCase):
|
|
|
76
76
|
TEST_METADATA[k] = v
|
|
77
77
|
|
|
78
78
|
def test_conversion_arff(self):
|
|
79
|
+
self.assertTrue(is_arff(TEST_ARFF))
|
|
79
80
|
# DSFF to ARFF
|
|
80
81
|
create_test_dsff()
|
|
81
82
|
with DSFF(TEST) as f:
|
|
@@ -84,10 +85,11 @@ class TestDsff(TestCase):
|
|
|
84
85
|
with DSFF(mode='w+') as f:
|
|
85
86
|
self.assertRaises(ValueError, f.to_arff, ())
|
|
86
87
|
# ARFF to DSFF
|
|
87
|
-
arff = TEST_BASENAME + ".arff"
|
|
88
88
|
# use a test ARFF with a comment line
|
|
89
|
-
with open(arff, 'w') as f:
|
|
89
|
+
with open(arff := f"{TEST_BASENAME}.arff", 'w') as f:
|
|
90
90
|
f.write(TEST_ARFF)
|
|
91
|
+
self.assertTrue(is_arff(arff))
|
|
92
|
+
self.assertIsInstance(load_arff(arff), dict)
|
|
91
93
|
with DSFF() as f:
|
|
92
94
|
f.from_arff(TEST_BASENAME)
|
|
93
95
|
# test for multiple error scenarios
|
|
@@ -123,25 +125,18 @@ class TestDsff(TestCase):
|
|
|
123
125
|
d.insert(10, d.pop(6))
|
|
124
126
|
|
|
125
127
|
def test_conversion_csv(self):
|
|
128
|
+
self.assertFalse(is_csv(b"PK\x03\x04\x14\x00\x00\x00\x08\x00P\xb3T\\F\xc7MH"))
|
|
126
129
|
# DSFF to CSV
|
|
127
130
|
create_test_dsff()
|
|
128
131
|
with DSFF(TEST) as f:
|
|
129
132
|
self.assertIsNotNone(f.to_csv(text=True))
|
|
130
133
|
self.assertIsNone(f.to_csv())
|
|
134
|
+
self.assertTrue(is_csv(csv := f"{TEST_BASENAME}.csv"))
|
|
135
|
+
self.assertIsInstance(load_csv(csv), dict)
|
|
131
136
|
# CSV to DSFF
|
|
132
137
|
with DSFF() as f:
|
|
133
138
|
f.from_csv(TEST_BASENAME)
|
|
134
139
|
|
|
135
|
-
def test_conversion_db(self):
|
|
136
|
-
# DSFF to SQL database
|
|
137
|
-
create_test_dsff()
|
|
138
|
-
with DSFF(TEST) as f:
|
|
139
|
-
self.assertIsNotNone(f.to_db(text=True))
|
|
140
|
-
self.assertIsNone(f.to_db())
|
|
141
|
-
# SQL database to DSFF
|
|
142
|
-
with DSFF() as f:
|
|
143
|
-
f.from_db(TEST_BASENAME)
|
|
144
|
-
|
|
145
140
|
def test_conversion_dataset(self):
|
|
146
141
|
# DSFF to FilelessDataset
|
|
147
142
|
create_test_dsff()
|
|
@@ -150,6 +145,9 @@ class TestDsff(TestCase):
|
|
|
150
145
|
# FilelessDataset to DSFF
|
|
151
146
|
with DSFF() as f:
|
|
152
147
|
f.from_dataset(TEST_BASENAME)
|
|
148
|
+
self.assertTrue(is_dataset(TEST_BASENAME))
|
|
149
|
+
self.assertRaises(BadInputData, load_dataset, 0)
|
|
150
|
+
self.assertIsInstance(load_dataset(TEST_BASENAME), dict)
|
|
153
151
|
# FilelessDataset to DSFF (bad input dataset)
|
|
154
152
|
os.remove(os.path.join(TEST_BASENAME, "metadata.json"))
|
|
155
153
|
with DSFF() as f:
|
|
@@ -161,6 +159,23 @@ class TestDsff(TestCase):
|
|
|
161
159
|
with DSFF() as f:
|
|
162
160
|
self.assertRaises(BadInputData, f.from_dataset, TEST_BASENAME)
|
|
163
161
|
os.remove(TEST_BASENAME)
|
|
162
|
+
os.makedirs(TEST_BASENAME)
|
|
163
|
+
self.assertFalse(is_dataset(TEST_BASENAME))
|
|
164
|
+
rmdir(TEST_BASENAME)
|
|
165
|
+
|
|
166
|
+
def test_conversion_db(self):
|
|
167
|
+
self.assertFalse(is_db(b"PK\x03\x04\x14\x00\x00\x00\x08\x00P\xb3T\\F\xc7MH"))
|
|
168
|
+
self.assertFalse(is_db(b"SQLite format 3\x00\x10\x00\x01\x01\x00@"))
|
|
169
|
+
# DSFF to SQL database
|
|
170
|
+
create_test_dsff()
|
|
171
|
+
with DSFF(TEST) as f:
|
|
172
|
+
self.assertIsNotNone(f.to_db(text=True))
|
|
173
|
+
self.assertIsNone(f.to_db())
|
|
174
|
+
self.assertTrue(is_db(db := f"{TEST_BASENAME}.db"))
|
|
175
|
+
self.assertIsInstance(load_db(db), dict)
|
|
176
|
+
# SQL database to DSFF
|
|
177
|
+
with DSFF() as f:
|
|
178
|
+
f.from_db(TEST_BASENAME)
|
|
164
179
|
|
|
165
180
|
def test_conversion_pyarrow_formats(self):
|
|
166
181
|
if PYARROW:
|
|
@@ -172,13 +187,17 @@ class TestDsff(TestCase):
|
|
|
172
187
|
f.from_dataset(path=TEST_BASENAME)
|
|
173
188
|
self.assertIsNone(getattr(f, f"to_{fmt}")(TEST_BASENAME))
|
|
174
189
|
self.assertIsNotNone(getattr(f, f"to_{fmt}")(text=True))
|
|
190
|
+
is_ = globals()[f'is_{fmt}']
|
|
191
|
+
self.assertTrue(is_(fn := f"{TEST_BASENAME}.{fmt}"))
|
|
192
|
+
self.assertFalse(is_(b"PK\x03\x04\x14\x00\x00\x00\x08\x00P\xb3T\\F\xc7MH"))
|
|
193
|
+
self.assertIsInstance(globals()[f'load_{fmt}'](fn), dict)
|
|
175
194
|
with DSFF(INMEMORY) as f:
|
|
176
195
|
self.assertIsNone(getattr(f, f"from_{fmt}")(TEST_BASENAME))
|
|
177
196
|
f.to_dataset(path=TEST_BASENAME)
|
|
178
197
|
os.remove(f"{TEST_BASENAME}.{fmt}")
|
|
179
198
|
rmdir(TEST_BASENAME)
|
|
180
199
|
# test without the 'label' column
|
|
181
|
-
with open(TEST_BASENAME
|
|
200
|
+
with open(f"{TEST_BASENAME}.arff", 'w') as f:
|
|
182
201
|
f.write(TEST_ARFF.replace("class", "test").replace("?", "-1"))
|
|
183
202
|
with DSFF() as f:
|
|
184
203
|
f.from_arff(TEST_BASENAME)
|
|
@@ -190,10 +209,13 @@ class TestDsff(TestCase):
|
|
|
190
209
|
self.assertIsNone(f.to_dataset())
|
|
191
210
|
f.name = TEST
|
|
192
211
|
self.assertIsNotNone(f.headers)
|
|
212
|
+
self.assertTrue(DSFF.is_valid_format(TEST_BASENAME))
|
|
193
213
|
with DSFF(INMEMORY) as f:
|
|
194
214
|
self.assertRaises(EmptyDsffFile, f.to_arff)
|
|
195
215
|
self.assertIsNone(f.from_dataset(TEST_BASENAME))
|
|
196
216
|
self.assertIsNone(f.to_arff())
|
|
217
|
+
self.assertRaises(ValueError, DSFF.is_valid_format, 0)
|
|
218
|
+
self.assertTrue(DSFF.is_valid_format(f"undefined.arff"))
|
|
197
219
|
os.remove(f"undefined.arff")
|
|
198
220
|
rmdir(TEST_BASENAME)
|
|
199
221
|
with DSFF(INMEMORY) as f:
|
dsff-1.2.0/docs/coverage.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="114" height="20" role="img" aria-label="coverage: 89.26%"><title>coverage: 89.26%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="114" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="53" height="20" fill="#97ca00"/><rect width="114" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="315" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">coverage</text><text x="315" y="140" transform="scale(.1)" fill="#fff" textLength="510">coverage</text><text aria-hidden="true" x="865" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">89.26%</text><text x="865" y="140" transform="scale(.1)" fill="#fff" textLength="430">89.26%</text></g></svg>
|
dsff-1.2.0/src/dsff/VERSION.txt
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.2.0
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# -*- coding: UTF-8 -*-
|
|
2
|
-
import csv as csvmod
|
|
3
|
-
import json
|
|
4
|
-
import re
|
|
5
|
-
from ast import literal_eval
|
|
6
|
-
from io import BytesIO, StringIO
|
|
7
|
-
from os import makedirs, remove
|
|
8
|
-
from os.path import basename, expanduser, isfile, isdir, join, splitext
|
|
9
|
-
try: # pragma: no cover
|
|
10
|
-
import pyarrow
|
|
11
|
-
import pyarrow.feather as feather
|
|
12
|
-
import pyarrow.orc as orc
|
|
13
|
-
import pyarrow.parquet as parquet
|
|
14
|
-
import pandas
|
|
15
|
-
PYARROW = True
|
|
16
|
-
except ImportError: # pragma: no cover
|
|
17
|
-
PYARROW = False
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
__all__ = ["basename", "csvmod", "expanduser", "isfile", "isdir", "join", "json", "literal_eval", "makedirs",
|
|
21
|
-
"remove", "splitext", "re", "BytesIO", "StringIO",
|
|
22
|
-
"CSV_DELIMITER", "DEFAULT_EXCL", "INMEMORY", "META_EXCL", "MISSING_TOKEN", "PYARROW", "TARGET_NAME"]
|
|
23
|
-
if PYARROW:
|
|
24
|
-
__all__ += ["feather", "orc", "pandas", "pyarrow", "parquet"]
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
CSV_DELIMITER = ";"
|
|
28
|
-
DEFAULT_EXCL = ("hash", "realpath", "format", "size", "ctime", "mtime") # origin: executables used in the Packing Box
|
|
29
|
-
INMEMORY = "<memory>"
|
|
30
|
-
META_EXCL = ["created", "modified", "revision"]
|
|
31
|
-
MISSING_TOKEN = "?"
|
|
32
|
-
TARGET_NAME = "label"
|
|
33
|
-
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# -*- coding: UTF-8 -*-
|
|
2
|
-
from .__common__ import *
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
__all__ = ["from_csv", "to_csv"]
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def from_csv(dsff, path=None, exclude=DEFAULT_EXCL):
|
|
9
|
-
""" Populate the DSFF file from a CSV file. """
|
|
10
|
-
dsff.write(path)
|
|
11
|
-
features = {}
|
|
12
|
-
for headers in dsff['data'].rows:
|
|
13
|
-
for header in headers:
|
|
14
|
-
if header.value in exclude:
|
|
15
|
-
continue
|
|
16
|
-
features[header.value] = ""
|
|
17
|
-
break
|
|
18
|
-
dsff.write(features=features)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def to_csv(dsff, path=None, text=False):
|
|
22
|
-
""" Create a CSV from the data worksheet, saved as a file or output as a string. """
|
|
23
|
-
with (StringIO() if text else open(path, 'w+')) as f:
|
|
24
|
-
writer = csvmod.writer(f, delimiter=";")
|
|
25
|
-
for row in dsff.data:
|
|
26
|
-
writer.writerow(row)
|
|
27
|
-
if text:
|
|
28
|
-
return f.getvalue()
|
|
29
|
-
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# -*- coding: UTF-8 -*-
|
|
2
|
-
from .__common__ import *
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
__all__ = ["from_dataset", "to_dataset"]
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def from_dataset(dsff, path=None):
|
|
9
|
-
""" Populate the DSFF file from a Dataset structure. """
|
|
10
|
-
if not isdir(path):
|
|
11
|
-
raise BadInputData("Not a folder")
|
|
12
|
-
else:
|
|
13
|
-
if len(missing := [f for f in ["data.csv", "features.json", "metadata.json"] if not isfile(join(path, f))]) > 0:
|
|
14
|
-
raise BadInputData(f"Not a valid dataset folder (missing: {', '.join(missing)})")
|
|
15
|
-
dsff.write(path)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def to_dataset(dsff, path=None):
|
|
19
|
-
""" Create a dataset folder according to the following structure ;
|
|
20
|
-
name
|
|
21
|
-
+-- data.csv
|
|
22
|
-
+-- features.json
|
|
23
|
-
+-- metadata.json
|
|
24
|
-
"""
|
|
25
|
-
makedirs(path, exist_ok=True)
|
|
26
|
-
# handle data first
|
|
27
|
-
dsff.logger.debug("> making data.csv...")
|
|
28
|
-
with open(join(path, "data.csv"), 'w+') as f:
|
|
29
|
-
writer = csvmod.writer(f, delimiter=";")
|
|
30
|
-
for cells in dsff['data'].rows:
|
|
31
|
-
writer.writerow([dsff._DSFF__eval(c.value) for c in cells])
|
|
32
|
-
# then handle features dictionary
|
|
33
|
-
dsff.logger.debug("> making features.json...")
|
|
34
|
-
with open(join(path, "features.json"), 'w+') as f:
|
|
35
|
-
json.dump(dsff.features, f, indent=2)
|
|
36
|
-
# finally handle metadata dictionary
|
|
37
|
-
dsff.logger.debug("> making metadata.json...")
|
|
38
|
-
with open(join(path, "metadata.json"), 'w+') as f:
|
|
39
|
-
json.dump(dsff.metadata, f, indent=2)
|
|
40
|
-
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# -*- coding: UTF-8 -*-
|
|
2
|
-
from .__common__ import *
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
__all__ = []
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def _nowrite(m):
|
|
9
|
-
raise NotImplementedError(f"none of {m}.write_table and {m}.write_{m} is implemented")
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
for module in ["feather", "orc", "parquet"]:
|
|
13
|
-
__all__ += [f"from_{module}", f"to_{module}"]
|
|
14
|
-
def gen_func(m):
|
|
15
|
-
def from_(dsff, path=None, exclude=DEFAULT_EXCL):
|
|
16
|
-
dataset = globals()[m].read_table(path)
|
|
17
|
-
dsff.write(data=[dataset.schema.names] + [list(r.values()) for r in dataset.to_pylist()],
|
|
18
|
-
metadata=literal_eval(dataset.schema.metadata.pop(b'__metadata__', b"{}").decode()),
|
|
19
|
-
features={k.decode(): v.decode() for k, v in dataset.schema.metadata.items()})
|
|
20
|
-
from_.__name__ = f"from_{m}"
|
|
21
|
-
def to_(dsff, path=None, text=False):
|
|
22
|
-
with (BytesIO() if text else open(path, 'wb+')) as f:
|
|
23
|
-
getattr(globals()[m], "write_table", getattr(globals()[m], f"write_{m}", _nowrite))(dsff._to_table(), f)
|
|
24
|
-
if text:
|
|
25
|
-
return f.getvalue()
|
|
26
|
-
to_.__name__ = f"to_{m}"
|
|
27
|
-
return from_, to_
|
|
28
|
-
globals()[f'from_{module}'], globals()[f'to_{module}'] = gen_func(module)
|
|
29
|
-
|
|
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
|