Skip to content

Commit 9517e0b

Browse files
authored
WIP - Document the way to write reader and writer plugins (#91)
* 📚 add custom plugin writer * This is an auto-commit, updating project meta data, such as changelog.rst, contributors.rst * 📚 update writer plugin * 📚 update plugin writer and reader documentation * 📚 telling the location of the example codes Co-authored-by: chfw <chfw@users.noreply.github.com>
1 parent fa80887 commit 9517e0b

File tree

10 files changed

+180
-26
lines changed

10 files changed

+180
-26
lines changed

.moban.d/docs/source/index.rst.jj2

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ get_data(.., library='pyexcel-ods')
9191
csvz
9292
sqlalchemy
9393
django
94+
options
9495
extensions
9596

9697

docs/source/conf.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,16 @@
2626
copyright = '2015-2020 Onni Software Ltd.'
2727
author = 'chfw'
2828
# The short X.Y version
29-
version = '0.6.0'
29+
version = '0.5.20'
3030
# The full version, including alpha/beta/rc tags
31-
release = '0.5.20'
31+
release = '0.6.0'
3232

3333
# -- General configuration ---------------------------------------------------
3434

3535
# Add any Sphinx extension module names here, as strings. They can be
3636
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
3737
# ones.
38-
extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode',]
38+
extensions = [ 'sphinx.ext.autosummary', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode', 'sphinx.ext.autodoc',]
3939

4040
# Add any paths that contain templates here, relative to this directory.
4141
templates_path = ['_templates']

docs/source/extensions.rst

Lines changed: 101 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,129 @@
11
Extend pyexcel-io Tutorial
22
================================================================================
33

4-
pyexcel-io itself comes with csv support.
4+
You are welcome extend pyexcel-io to read and write more tabular formats. In
5+
github repo, you will find two examples in `examples` folder. This section
6+
explains its implementations to help you write yours.
7+
8+
.. note::
9+
10+
No longer, you will need to do explicit imports for pyexcel-io extensions.
11+
Instead, you install them and manage them via pip.
512

613
Reader
714
--------------------------------------------------------------------------------
815

916
Suppose we have a yaml file, containing a dictionary where the values are
10-
two dimensional array. The task is write reader plugin to pyexcel-io so that
11-
we can use get_data() to read it out.
12-
13-
Example yaml data::
17+
two dimensional array. The task is to write a reader plugin to pyexcel-io so that
18+
we can use get_data() to read yaml file out.
1419

1520
.. literalinclude:: ../../examples/test.yaml
1621
:language: yaml
17-
18-
Example code::
1922

20-
.. literalinclude:: ../../examples/custom_yeaml_reader.py
23+
**Implement IReader**
24+
25+
First, let's impolement reader interface as below. Three implementations are required:
26+
27+
1. `content_array` attribute, is expected to be a list of `NamedContent`
28+
2. `read_sheet` function, read sheet content by its index.
29+
3. `close` function, to clean up any file handle
30+
31+
.. literalinclude:: ../../examples/custom_yaml_reader.py
2132
:language: python
33+
:lines: 19-33
34+
35+
**Implement ISheet**
2236

37+
`YourSingleSheet` makes this simple task complex in order to show case its inner
38+
workings. Two abstract functions require implementation:
39+
40+
1. `row_iterator`: should return a row: either content arry or content index as long as
41+
`column_iterator` understands
42+
43+
2. `column_iterator`: should return cell values one by one.
44+
45+
.. literalinclude:: ../../examples/custom_yaml_reader.py
46+
:language: python
47+
:lines: 8-16
48+
49+
50+
**Plug in pyexcel-io**
51+
52+
Last thing is to register with pyexcel-io about your new reader. `relative_plugin_class_path`
53+
meant reference from current module, how to refer to `YourReader`. `locations` meant
54+
the physical presence of the data source: "file", "memory" or "content". "file" means
55+
files on physical disk. "memory" means a file stream. "content" means a string buffer.
56+
`stream_type` meant the type of the stream: binary for BytesIO and text for StringIO.
57+
58+
.. literalinclude:: ../../examples/custom_yaml_reader.py
59+
:language: python
60+
:lines: 36-41
61+
62+
63+
**Test your reader **
64+
65+
Let's run the following code and see if it works.
66+
67+
.. literalinclude:: ../../examples/custom_yaml_reader.py
68+
:language: python
69+
:lines: 43-45
2370

2471
Writer
2572
--------------------------------------------------------------------------------
2673

74+
Now for the writer, let's write a pyexcel-io writer that write a dictionary of
75+
two dimentaional arrays back into a yaml file seen above.
2776

77+
** Implement IWriter **
2878

29-
Working with xls, xlsx, and ods formats
30-
================================================================================
79+
Two abstract functions are required:
3180

32-
.. note::
81+
1. `create_sheet` creates a native sheet by sheet name, that understands how to code up the native sheet. Interestingly, it returns your sheet.
82+
2. `close` function closes file handle if any.
83+
84+
.. literalinclude:: ../../examples/custom_yaml_writer.py
85+
:language: python
86+
:lines: 18-30
87+
88+
** Implement ISheetWriter **
89+
90+
It is imagined that you will have your own sheet writer. You simply need to figure
91+
out how to write a row. Row by row write action was already written by `ISheetWrier`.
92+
93+
94+
.. literalinclude:: ../../examples/custom_yaml_writer.py
95+
:language: python
96+
:lines: 7-14
97+
98+
**Plug in pyexcel-io**
99+
100+
Like the reader plugin, we register a writer.
101+
102+
.. literalinclude:: ../../examples/custom_yaml_writer.py
103+
:language: python
104+
:lines: 33-38
105+
106+
**Test It**
107+
108+
Let's run the following code and please examine `mytest.yaml` yourself.
109+
110+
.. literalinclude:: ../../examples/custom_yaml_writer.py
111+
:language: python
112+
:lines: 40-46
33113

34-
No longer, you will need to do explicit imports for pyexcel-io extensions.
35-
Instead, you install them and manage them via pip.
36114

37-
Work with physical file
115+
116+
Other pyexcel-io plugins
38117
-----------------------------------------------------------------------------
39118

119+
Get xls support
120+
121+
.. code-block::
122+
123+
124+
$ pip install pyexcel-xls
125+
126+
40127
Here's what is needed::
41128

42129
>>> from pyexcel_io import save_data
@@ -71,7 +158,6 @@ And you can also get the data back::
71158

72159
The same applies to :meth:`pyexcel_io.get_data`.
73160

74-
75161
Other formats
76162
-----------------------------------------------------------------------------
77163

docs/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ get_data(.., library='pyexcel-ods')
193193
csvz
194194
sqlalchemy
195195
django
196+
options
196197
extensions
197198

198199

examples/custom_yaml_writer.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import yaml
2+
from pyexcel_io import save_data
3+
from pyexcel_io.plugins import IOPluginInfoChainV2
4+
from pyexcel_io.plugin_api import IWriter, ISheetWriter
5+
6+
7+
class MySheetWriter(ISheetWriter):
8+
def __init__(self, sheet_reference):
9+
self.native_sheet = sheet_reference
10+
11+
def write_row(self, data_row):
12+
self.native_sheet.append(data_row)
13+
14+
def close(self):
15+
pass
16+
17+
18+
class MyWriter(IWriter):
19+
def __init__(self, file_name, file_type, **keywords):
20+
self.file_name = file_name
21+
self.content = {}
22+
23+
def create_sheet(self, name):
24+
array = []
25+
self.content[name] = array
26+
return MySheetWriter(array)
27+
28+
def close(self):
29+
with open(self.file_name, "w") as f:
30+
f.write(yaml.dump(self.content, default_flow_style=False))
31+
32+
33+
IOPluginInfoChainV2(__name__).add_a_writer(
34+
relative_plugin_class_path="MyWriter",
35+
locations=["file"],
36+
file_types=["yaml"],
37+
stream_type="text",
38+
)
39+
40+
if __name__ == "__main__":
41+
data_dict = {
42+
"sheet 1": [[1, 3, 4], [2, 4, 9]],
43+
"sheet 2": [["B", "C", "D"]],
44+
}
45+
46+
save_data("mytest.yaml", data_dict)

pyexcel-io.yml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ overrides: "pyexcel.yaml"
22
project: "pyexcel-io"
33
name: pyexcel-io
44
nick_name: io
5-
version: 0.6.0
5+
version: 0.5.20
66
current_version: 0.6.0
7-
release: 0.5.20
7+
release: 0.6.0
88
copyright_year: 2015-2020
99
moban_command: false
1010
is_on_conda: true
@@ -31,6 +31,12 @@ keywords:
3131
- csvz
3232
- django
3333
- sqlalchemy
34+
sphinx_extensions:
35+
- sphinx.ext.autosummary
36+
- sphinx.ext.doctest
37+
- sphinx.ext.intersphinx
38+
- sphinx.ext.viewcode
39+
- sphinx.ext.autodoc
3440
description: A python library to read and write structured data in csv, zipped csv format and to/from databases
3541
python_requires: ">=3.6"
3642
min_python_version: "3.6"

pyexcel_io/plugin_api/abstract_reader.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ class IReader(object):
99
"""
1010

1111
def read_sheet(self, sheet_index) -> ISheet:
12-
raise NotImplementedError("")
12+
raise NotImplementedError("Read the sheet by index")
1313

1414
def sheet_names(self):
1515
return [content.name for content in self.content_array]
1616

1717
def __len__(self):
1818
return len(self.content_array)
19+
20+
def close(self):
21+
raise NotImplementedError("Close the file")

pyexcel_io/plugin_api/abstract_sheet.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
class ISheet(object):
22
def row_iterator(self):
3-
raise NotImplementedError("")
3+
raise NotImplementedError("iterate each row")
44

55
def column_iterator(self, row):
6-
raise NotImplementedError("")
6+
raise NotImplementedError("iterate each column at a given row")
77

88

99
class ISheetWriter(object):
@@ -16,3 +16,6 @@ def write_array(self, table):
1616
"""
1717
for row in table:
1818
self.write_row(row)
19+
20+
def close(self):
21+
raise NotImplementedError("How would you close your file")

setup.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"format and to/from databases"
4141
)
4242
URL = "https://github.com/pyexcel/pyexcel-io"
43-
DOWNLOAD_URL = "%s/archive/0.5.20.tar.gz" % URL
43+
DOWNLOAD_URL = "%s/archive/0.6.0.tar.gz" % URL
4444
FILES = ["README.rst", "CHANGELOG.rst"]
4545
KEYWORDS = [
4646
"python",
@@ -85,8 +85,8 @@
8585
}
8686
# You do not need to read beyond this line
8787
PUBLISH_COMMAND = "{0} setup.py sdist bdist_wheel upload -r pypi".format(sys.executable)
88-
GS_COMMAND = ("gs pyexcel-io v0.5.20 " +
89-
"Find 0.5.20 in changelog for more details")
88+
GS_COMMAND = ("gs pyexcel-io v0.6.0 " +
89+
"Find 0.6.0 in changelog for more details")
9090
NO_GS_MESSAGE = ("Automatic github release is disabled. " +
9191
"Please install gease to enable it.")
9292
UPLOAD_FAILED_MSG = (

tests/test_plugin_api.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ def setUp(self):
2424
def test_write_row(self):
2525
self.isheet_writer.write_row([1, 2])
2626

27+
@raises(NotImplementedError)
28+
def test_close(self):
29+
self.isheet_writer.close()
30+
2731

2832
class TestIReader:
2933
def setUp(self):
@@ -33,6 +37,10 @@ def setUp(self):
3337
def test_read_sheet(self):
3438
self.ireader.read_sheet(1)
3539

40+
@raises(NotImplementedError)
41+
def test_close(self):
42+
self.ireader.close()
43+
3644

3745
class TestIWriter:
3846
def setUp(self):

0 commit comments

Comments
 (0)