dsff 1.2.0__tar.gz → 1.2.2__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.2}/PKG-INFO +31 -4
- {dsff-1.2.0 → dsff-1.2.2}/README.md +26 -1
- dsff-1.2.2/docs/coverage.svg +1 -0
- {dsff-1.2.0 → dsff-1.2.2}/pyproject.toml +2 -3
- dsff-1.2.2/src/dsff/VERSION.txt +1 -0
- dsff-1.2.2/src/dsff/formats/__common__.py +56 -0
- {dsff-1.2.0 → dsff-1.2.2}/src/dsff/formats/__init__.py +15 -0
- {dsff-1.2.0 → dsff-1.2.2}/src/dsff/formats/arff.py +76 -61
- {dsff-1.2.0 → dsff-1.2.2}/src/dsff/formats/csv.py +12 -1
- {dsff-1.2.0 → dsff-1.2.2}/src/dsff/formats/dataset.py +16 -3
- {dsff-1.2.0 → dsff-1.2.2}/src/dsff/formats/db.py +31 -1
- {dsff-1.2.0 → dsff-1.2.2}/src/dsff/formats/pa.py +10 -3
- {dsff-1.2.0 → dsff-1.2.2/src/dsff.egg-info}/PKG-INFO +31 -4
- dsff-1.2.2/src/dsff.egg-info/requires.txt +7 -0
- {dsff-1.2.0 → dsff-1.2.2}/tests/test_dsff.py +29 -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.egg-info/requires.txt +0 -4
- {dsff-1.2.0 → dsff-1.2.2}/.coveragerc +0 -0
- {dsff-1.2.0 → dsff-1.2.2}/.github/workflows/python-package.yml +0 -0
- {dsff-1.2.0 → dsff-1.2.2}/.gitignore +0 -0
- {dsff-1.2.0 → dsff-1.2.2}/.readthedocs.yml +0 -0
- {dsff-1.2.0 → dsff-1.2.2}/LICENSE +0 -0
- {dsff-1.2.0 → dsff-1.2.2}/docs/mkdocs.yml +0 -0
- {dsff-1.2.0 → dsff-1.2.2}/docs/pages/imgs/dsff-from.png +0 -0
- {dsff-1.2.0 → dsff-1.2.2}/docs/pages/imgs/dsff-to.png +0 -0
- {dsff-1.2.0 → dsff-1.2.2}/docs/pages/imgs/icon.png +0 -0
- {dsff-1.2.0 → dsff-1.2.2}/docs/pages/imgs/logo.png +0 -0
- {dsff-1.2.0 → dsff-1.2.2}/docs/pages/index.md +0 -0
- {dsff-1.2.0 → dsff-1.2.2}/docs/pages/usage.md +0 -0
- {dsff-1.2.0 → dsff-1.2.2}/docs/requirements.txt +0 -0
- {dsff-1.2.0 → dsff-1.2.2}/pytest.ini +0 -0
- {dsff-1.2.0 → dsff-1.2.2}/requirements.txt +0 -0
- {dsff-1.2.0 → dsff-1.2.2}/setup.cfg +0 -0
- {dsff-1.2.0 → dsff-1.2.2}/src/dsff/__info__.py +0 -0
- {dsff-1.2.0 → dsff-1.2.2}/src/dsff/__init__.py +0 -0
- {dsff-1.2.0 → dsff-1.2.2}/src/dsff.egg-info/SOURCES.txt +0 -0
- {dsff-1.2.0 → dsff-1.2.2}/src/dsff.egg-info/dependency_links.txt +0 -0
- {dsff-1.2.0 → dsff-1.2.2}/src/dsff.egg-info/top_level.txt +0 -0
- {dsff-1.2.0 → dsff-1.2.2}/tests/__init__.py +0 -0
- {dsff-1.2.0 → dsff-1.2.2}/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.2
|
|
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.2
|
|
@@ -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_"):
|
|
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,66 @@
|
|
|
2
2
|
from .__common__ import *
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
__all__ = ["from_arff", "to_arff"]
|
|
5
|
+
__all__ = ["from_arff", "is_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):
|
|
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.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).group(1).strip("'\"")
|
|
60
19
|
continue
|
|
61
|
-
|
|
62
|
-
raise BadInputData(f"{pf}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
+
n_cols = len(d[0])
|
|
38
|
+
continue
|
|
39
|
+
if l.startswith("@ATTRIBUTE "):
|
|
40
|
+
if not attributes[0]:
|
|
41
|
+
attributes[0] = True
|
|
42
|
+
if len(d) == 0:
|
|
43
|
+
# start the attributes block
|
|
44
|
+
d.append([])
|
|
45
|
+
if attributes[1]:
|
|
46
|
+
raise BadInputData(f"{pf}found @ATTRIBUTE out of the attributes block)")
|
|
47
|
+
try:
|
|
48
|
+
header = re.match(r"@ATTRIBUTE\s+([^\s]+)\s+[A-Z]+$", l).group(1)
|
|
49
|
+
if header == "class":
|
|
50
|
+
header = target
|
|
51
|
+
d[0].append(header)
|
|
52
|
+
continue
|
|
53
|
+
except AttributeError:
|
|
54
|
+
raise BadInputData(f"{pf}failed on @ATTRIBUTE (bad type)")
|
|
55
|
+
if not data:
|
|
56
|
+
if l == "@DATA":
|
|
57
|
+
data = True
|
|
58
|
+
continue
|
|
59
|
+
else:
|
|
60
|
+
raise BadInputData(f"{pf}did not find @DATA where expected")
|
|
61
|
+
row = list(map(lambda x: x.strip("'\""), re.split(r",\s+", l)))
|
|
62
|
+
if len(row) != n_cols:
|
|
63
|
+
raise BadInputData(f"{pf}this row does not match the number of columns")
|
|
64
|
+
d.append(row)
|
|
67
65
|
for i in range(n_cols):
|
|
68
66
|
values = []
|
|
69
67
|
for j, row in enumerate(d):
|
|
@@ -76,9 +74,26 @@ def from_arff(dsff, path=None, target=TARGET_NAME, missing=MISSING_TOKEN):
|
|
|
76
74
|
if "".join(sorted(set(values))) in ["0", "1", "01"]:
|
|
77
75
|
for j, row in enumerate(d):
|
|
78
76
|
if j > 0:
|
|
79
|
-
row[i] = {'0': "False", '1': "True"}[row[i]]
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
row[i] = {'0': "False", '1': "True"}[row[i]]
|
|
78
|
+
return d, features, metadata, title
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def from_arff(dsff, path=None, target=TARGET_NAME, missing=MISSING_TOKEN):
|
|
82
|
+
""" Populate the DSFF file from an ARFF file. """
|
|
83
|
+
with open(path) as f:
|
|
84
|
+
d, ft, md, t = _parse(f, target, missing)
|
|
85
|
+
dsff['title'] = t
|
|
86
|
+
dsff.write(data=d, features=ft, metadata=md)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@text_or_path
|
|
90
|
+
def is_arff(text):
|
|
91
|
+
""" Check if the input text or path is a valid ARFF. """
|
|
92
|
+
try:
|
|
93
|
+
_parse(ensure_str(text))
|
|
94
|
+
return True
|
|
95
|
+
except (BadInputData, UnicodeDecodeError):
|
|
96
|
+
return False
|
|
82
97
|
|
|
83
98
|
|
|
84
99
|
def to_arff(dsff, path=None, target=TARGET_NAME, exclude=DEFAULT_EXCL, missing=MISSING_TOKEN, text=False):
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
from .__common__ import *
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
__all__ = ["from_csv", "to_csv"]
|
|
5
|
+
__all__ = ["from_csv", "is_csv", "to_csv"]
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def from_csv(dsff, path=None, exclude=DEFAULT_EXCL):
|
|
@@ -18,6 +18,17 @@ def from_csv(dsff, path=None, exclude=DEFAULT_EXCL):
|
|
|
18
18
|
dsff.write(features=features)
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
@text_or_path
|
|
22
|
+
def is_csv(text):
|
|
23
|
+
""" Check if the input text or path is a valid CSV. """
|
|
24
|
+
try:
|
|
25
|
+
dialect = csvmod.Sniffer().sniff(text := ensure_str(text))
|
|
26
|
+
csvmod.Sniffer().has_header(text)
|
|
27
|
+
return True
|
|
28
|
+
except (csvmod.Error, UnicodeDecodeError):
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
|
|
21
32
|
def to_csv(dsff, path=None, text=False):
|
|
22
33
|
""" Create a CSV from the data worksheet, saved as a file or output as a string. """
|
|
23
34
|
with (StringIO() if text else open(path, 'w+')) as f:
|
|
@@ -2,19 +2,32 @@
|
|
|
2
2
|
from .__common__ import *
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
__all__ = ["from_dataset", "to_dataset"]
|
|
5
|
+
__all__ = ["from_dataset", "is_dataset", "to_dataset"]
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
def
|
|
9
|
-
""" Populate the DSFF file from a Dataset structure. """
|
|
8
|
+
def _parse(path):
|
|
10
9
|
if not isdir(path):
|
|
11
10
|
raise BadInputData("Not a folder")
|
|
12
11
|
else:
|
|
13
12
|
if len(missing := [f for f in ["data.csv", "features.json", "metadata.json"] if not isfile(join(path, f))]) > 0:
|
|
14
13
|
raise BadInputData(f"Not a valid dataset folder (missing: {', '.join(missing)})")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def from_dataset(dsff, path=None):
|
|
17
|
+
""" Populate the DSFF file from a Dataset structure. """
|
|
18
|
+
_parse(path)
|
|
15
19
|
dsff.write(path)
|
|
16
20
|
|
|
17
21
|
|
|
22
|
+
def is_dataset(path):
|
|
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
|
+
|
|
18
31
|
def to_dataset(dsff, path=None):
|
|
19
32
|
""" Create a dataset folder according to the following structure ;
|
|
20
33
|
name
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
from .__common__ import *
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
__all__ = ["from_db", "to_db"]
|
|
5
|
+
__all__ = ["from_db", "is_db", "to_db"]
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def from_db(dsff, path=None, exclude=DEFAULT_EXCL):
|
|
@@ -30,6 +30,36 @@ def from_db(dsff, path=None, exclude=DEFAULT_EXCL):
|
|
|
30
30
|
conn.close()
|
|
31
31
|
|
|
32
32
|
|
|
33
|
+
@text_or_path
|
|
34
|
+
def is_db(data):
|
|
35
|
+
""" Check if the input data or path is a valid SQL database. """
|
|
36
|
+
from sqlite3 import connect, Error
|
|
37
|
+
from sys import version_info
|
|
38
|
+
if not data.startswith(b"SQLite format 3\x00"):
|
|
39
|
+
return False
|
|
40
|
+
if version_info.minor > 10:
|
|
41
|
+
try:
|
|
42
|
+
with connect(":memory:") as c:
|
|
43
|
+
c.deserialize(data)
|
|
44
|
+
c.execute("PRAGMA schema_version;")
|
|
45
|
+
return True
|
|
46
|
+
except Error:
|
|
47
|
+
return False
|
|
48
|
+
else: # pragma: no cover
|
|
49
|
+
from os import close, write
|
|
50
|
+
from tempfile import mkstemp
|
|
51
|
+
fd, path = mkstemp(suffix=".db")
|
|
52
|
+
try:
|
|
53
|
+
write(fd, data)
|
|
54
|
+
close(fd)
|
|
55
|
+
fd = None
|
|
56
|
+
with connect(f"file:{path}?mode=ro&immutable=1", uri=True) as c:
|
|
57
|
+
c.execute("PRAGMA schema_version;").fetchone()
|
|
58
|
+
return True
|
|
59
|
+
except (Error, OSError):
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
|
|
33
63
|
def to_db(dsff, path=None, text=False, primary_index=0):
|
|
34
64
|
""" Create a SQLDB from the data worksheet, saved as a file or output as a string. """
|
|
35
65
|
from json import dumps
|
|
@@ -10,7 +10,7 @@ def _nowrite(m):
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
for module in ["feather", "orc", "parquet"]:
|
|
13
|
-
__all__ += [f"from_{module}", f"to_{module}"]
|
|
13
|
+
__all__ += [f"from_{module}", f"is_{module}", f"to_{module}"]
|
|
14
14
|
def gen_func(m):
|
|
15
15
|
def from_(dsff, path=None, exclude=DEFAULT_EXCL):
|
|
16
16
|
dataset = globals()[m].read_table(path)
|
|
@@ -18,12 +18,19 @@ for module in ["feather", "orc", "parquet"]:
|
|
|
18
18
|
metadata=literal_eval(dataset.schema.metadata.pop(b'__metadata__', b"{}").decode()),
|
|
19
19
|
features={k.decode(): v.decode() for k, v in dataset.schema.metadata.items()})
|
|
20
20
|
from_.__name__ = f"from_{m}"
|
|
21
|
+
def is_(data):
|
|
22
|
+
try:
|
|
23
|
+
globals()[m].read_table(pyarrow.BufferReader(data))
|
|
24
|
+
return True
|
|
25
|
+
except Exception:
|
|
26
|
+
return False
|
|
27
|
+
is_.__name__ = f"is_{m}"
|
|
21
28
|
def to_(dsff, path=None, text=False):
|
|
22
29
|
with (BytesIO() if text else open(path, 'wb+')) as f:
|
|
23
30
|
getattr(globals()[m], "write_table", getattr(globals()[m], f"write_{m}", _nowrite))(dsff._to_table(), f)
|
|
24
31
|
if text:
|
|
25
32
|
return f.getvalue()
|
|
26
33
|
to_.__name__ = f"to_{m}"
|
|
27
|
-
return from_, to_
|
|
28
|
-
globals()[f'from_{module}'], globals()[f'to_{module}'] = gen_func(module)
|
|
34
|
+
return from_, text_or_path(is_), to_
|
|
35
|
+
globals()[f'from_{module}'], globals()[f'is_{module}'], globals()[f'to_{module}'] = gen_func(module)
|
|
29
36
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dsff
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
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,10 @@ 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))
|
|
91
92
|
with DSFF() as f:
|
|
92
93
|
f.from_arff(TEST_BASENAME)
|
|
93
94
|
# test for multiple error scenarios
|
|
@@ -123,25 +124,17 @@ class TestDsff(TestCase):
|
|
|
123
124
|
d.insert(10, d.pop(6))
|
|
124
125
|
|
|
125
126
|
def test_conversion_csv(self):
|
|
127
|
+
self.assertFalse(is_csv(b"PK\x03\x04\x14\x00\x00\x00\x08\x00P\xb3T\\F\xc7MH"))
|
|
126
128
|
# DSFF to CSV
|
|
127
129
|
create_test_dsff()
|
|
128
130
|
with DSFF(TEST) as f:
|
|
129
131
|
self.assertIsNotNone(f.to_csv(text=True))
|
|
130
132
|
self.assertIsNone(f.to_csv())
|
|
133
|
+
self.assertTrue(is_csv(f"{TEST}.csv"))
|
|
131
134
|
# CSV to DSFF
|
|
132
135
|
with DSFF() as f:
|
|
133
136
|
f.from_csv(TEST_BASENAME)
|
|
134
137
|
|
|
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
138
|
def test_conversion_dataset(self):
|
|
146
139
|
# DSFF to FilelessDataset
|
|
147
140
|
create_test_dsff()
|
|
@@ -150,6 +143,7 @@ class TestDsff(TestCase):
|
|
|
150
143
|
# FilelessDataset to DSFF
|
|
151
144
|
with DSFF() as f:
|
|
152
145
|
f.from_dataset(TEST_BASENAME)
|
|
146
|
+
self.assertTrue(is_dataset(f"{TEST_BASENAME}"))
|
|
153
147
|
# FilelessDataset to DSFF (bad input dataset)
|
|
154
148
|
os.remove(os.path.join(TEST_BASENAME, "metadata.json"))
|
|
155
149
|
with DSFF() as f:
|
|
@@ -161,6 +155,22 @@ class TestDsff(TestCase):
|
|
|
161
155
|
with DSFF() as f:
|
|
162
156
|
self.assertRaises(BadInputData, f.from_dataset, TEST_BASENAME)
|
|
163
157
|
os.remove(TEST_BASENAME)
|
|
158
|
+
os.makedirs(TEST_BASENAME)
|
|
159
|
+
self.assertFalse(is_dataset(TEST_BASENAME))
|
|
160
|
+
rmdir(TEST_BASENAME)
|
|
161
|
+
|
|
162
|
+
def test_conversion_db(self):
|
|
163
|
+
self.assertFalse(is_db(b"PK\x03\x04\x14\x00\x00\x00\x08\x00P\xb3T\\F\xc7MH"))
|
|
164
|
+
self.assertFalse(is_db(b"SQLite format 3\x00\x10\x00\x01\x01\x00@"))
|
|
165
|
+
# DSFF to SQL database
|
|
166
|
+
create_test_dsff()
|
|
167
|
+
with DSFF(TEST) as f:
|
|
168
|
+
self.assertIsNotNone(f.to_db(text=True))
|
|
169
|
+
self.assertIsNone(f.to_db())
|
|
170
|
+
self.assertTrue(is_db(f"{TEST_BASENAME}.db"))
|
|
171
|
+
# SQL database to DSFF
|
|
172
|
+
with DSFF() as f:
|
|
173
|
+
f.from_db(TEST_BASENAME)
|
|
164
174
|
|
|
165
175
|
def test_conversion_pyarrow_formats(self):
|
|
166
176
|
if PYARROW:
|
|
@@ -172,13 +182,16 @@ class TestDsff(TestCase):
|
|
|
172
182
|
f.from_dataset(path=TEST_BASENAME)
|
|
173
183
|
self.assertIsNone(getattr(f, f"to_{fmt}")(TEST_BASENAME))
|
|
174
184
|
self.assertIsNotNone(getattr(f, f"to_{fmt}")(text=True))
|
|
185
|
+
is_ = globals()[f'is_{fmt}']
|
|
186
|
+
self.assertTrue(is_(f"{TEST_BASENAME}.{fmt}"))
|
|
187
|
+
self.assertFalse(is_(b"PK\x03\x04\x14\x00\x00\x00\x08\x00P\xb3T\\F\xc7MH"))
|
|
175
188
|
with DSFF(INMEMORY) as f:
|
|
176
189
|
self.assertIsNone(getattr(f, f"from_{fmt}")(TEST_BASENAME))
|
|
177
190
|
f.to_dataset(path=TEST_BASENAME)
|
|
178
191
|
os.remove(f"{TEST_BASENAME}.{fmt}")
|
|
179
192
|
rmdir(TEST_BASENAME)
|
|
180
193
|
# test without the 'label' column
|
|
181
|
-
with open(TEST_BASENAME
|
|
194
|
+
with open(f"{TEST_BASENAME}.arff", 'w') as f:
|
|
182
195
|
f.write(TEST_ARFF.replace("class", "test").replace("?", "-1"))
|
|
183
196
|
with DSFF() as f:
|
|
184
197
|
f.from_arff(TEST_BASENAME)
|
|
@@ -190,10 +203,13 @@ class TestDsff(TestCase):
|
|
|
190
203
|
self.assertIsNone(f.to_dataset())
|
|
191
204
|
f.name = TEST
|
|
192
205
|
self.assertIsNotNone(f.headers)
|
|
206
|
+
self.assertTrue(DSFF.is_valid_format(TEST_BASENAME))
|
|
193
207
|
with DSFF(INMEMORY) as f:
|
|
194
208
|
self.assertRaises(EmptyDsffFile, f.to_arff)
|
|
195
209
|
self.assertIsNone(f.from_dataset(TEST_BASENAME))
|
|
196
210
|
self.assertIsNone(f.to_arff())
|
|
211
|
+
self.assertRaises(ValueError, DSFF.is_valid_format, 0)
|
|
212
|
+
self.assertTrue(DSFF.is_valid_format(f"undefined.arff"))
|
|
197
213
|
os.remove(f"undefined.arff")
|
|
198
214
|
rmdir(TEST_BASENAME)
|
|
199
215
|
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
|
-
|
|
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
|