Skip to content

Commit

Permalink
Merge pull request xlwings#432 from ZoomerAnalytics/develop
Browse files Browse the repository at this point in the history
merge develop for v0.7.1 release
  • Loading branch information
fzumstein committed Apr 3, 2016
2 parents e45e1ea + 4c44441 commit 83b3d91
Show file tree
Hide file tree
Showing 42 changed files with 951 additions and 249 deletions.
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Links
-----

* Homepage: http://xlwings.org
* Quickstart: http://docs.xlwings.org/quickstart.html
* Quickstart: http://docs.xlwings.org/en/stable/quickstart.html
* Documentation: http://docs.xlwings.org
* Source Code: http://github.com/zoomeranalytics/xlwings

Expand Down
21 changes: 15 additions & 6 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,24 @@ API Documentation

The xlwings object model is very similar to the one used by Excel VBA but the hierarchy is flattened. An example:

| **VBA:**
| ``Workbooks("Book1").Sheets("Sheet1").Range("A1").Value = "Some Text"``
**VBA:**

| **xlwings:**
| ``wb = Workbook("Book1")``
| ``Range("Sheet1", "A1").value = "Some Text"``
.. code-block:: python
Workbooks("Book1").Sheets("Sheet1").Range("A1").Value = "Some Text"
.. module:: xlwings
**xlwings:**

.. code-block:: python
wb = Workbook("Book1")
Range("Sheet1", "A1").value = "Some Text"
Top-level functions
-------------------

.. automodule:: xlwings
:members: view

Application
-----------
Expand Down
3 changes: 2 additions & 1 deletion docs/connect_to_workbook.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ the need to constantly change between ``Workbook.caller()`` and one of the metho
User Defined Functions (UDFs)
-----------------------------

UDFs work differently and don't need the explicit instantiation of a ``Workbook``, see :ref:`udfs`
UDFs work differently and don't need the explicit instantiation of a ``Workbook``, see :ref:`udfs`.
However, ``xw.Workbook.caller()`` can be used in UDFs although just read-only.
181 changes: 172 additions & 9 deletions docs/converters.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.. _converters:

Converters
==========
Converters and Options
======================

Introduced with v0.7.0, converters define how Excel ranges and their values are converted both during
**reading** and **writing** operations. They also provide a consistent experience across **xlwings.Range** objects and
Expand Down Expand Up @@ -144,18 +144,17 @@ The following options can be set:

.. note:: The ``expand`` option is only available on ``Range`` objects as UDFs only allow to manipulate the calling cells.

Built-in converters
Built-in Converters
-------------------

xlwings offers several built-in converters that perform type conversion to **dictionaries**, **NumPy arrays**,
**Pandas Series** and **DataFrames**. These build on top of the default converter, so in most case the options
described above can be used in this context too (unless they are meaningless, for example the ``ndim`` in the case
**Pandas Series** and **DataFrames**. These build on top of the default converter, so in most cases the options
described above can be used in this context, too (unless they are meaningless, for example the ``ndim`` in the case
of a dictionary).

It is also possible to write and register custom converter for additional types. The documentation for doing this will
be provided in the near future.
It is also possible to write and register custom converter for additional types, see below.

The samples below may be used with both ``xlwings.Range`` objects and UDFs, but the samples may only show one version.
The samples below can be used with both ``xlwings.Range`` objects and UDFs even though only one version may be shown.

Dictionary converter
********************
Expand Down Expand Up @@ -273,4 +272,168 @@ The same sample for **UDF** (starting in ``Range('A13')`` on screenshot) looks l
@xw.ret(index=False)
def myfunction(x):
# x is a DataFrame, do something with it
return x
return x


xw.Range and 'raw' converters
*****************************

Technically speaking, these are "no-converters".

* If you need access to the ``xlwings.Range`` object directly, you can do::

@xw.func
@xw.arg('x', xw.Range)
def myfunction(x):
return x.formula

This returns x as ``xlwings.Range`` object, i.e. without applying any converters or options.

* The ``raw`` converter delivers the values unchanged from the underlying libraries (``pywin32`` on Windows and
``appscript`` on Mac), i.e. no sanitizing/cross-platform harmonizing of values are being made. This might be useful
in a few cases for efficiency reasons. E.g::

>>> Range('A1:B2').value
[[1.0, 'text'], [datetime.datetime(2016, 2, 1, 0, 0), None]]

>>> Range('A1:B2').options('raw').value
((1.0, 'text'), (pywintypes.datetime(2016, 2, 1, 0, 0, tzinfo=TimeZoneInfo('GMT Standard Time', True)), None))


.. _custom_converter:

Custom Converter
----------------

Here are the steps to implement your own converter:

* Inherit from ``xlwings.conversion.Converter``
* Implement both a ``read_value`` and ``write_value`` method as static- or classmethod:

* In ``read_value``, ``value`` is what the base converter returns: hence, if no
``base`` has been specified it arrives in the format of the default converter.
* In ``write_value``, ``value`` is the original object being written to Excel. It must be returned
in the format that the base converter expects. Again, if no ``base`` has been specified, this is the default
converter.

The ``options`` dictionary will contain all keyword arguments specified in
the ``Range.options`` method, e.g. when calling ``Range('A1').options(myoption='some value')`` or as specified in
the ``@arg`` and ``@ret`` decorator when using UDFs. Here is the basic structure::

from xlwings.conversion import Converter

class MyConverter(Converter):

@staticmethod
def read_value(value, options):
myoption = options.get('myoption', default_value)
return_value = value # Implement your conversion here
return return_value

@staticmethod
def write_value(value, options):
myoption = options.get('myoption', default_value)
return_value = value # Implement your conversion here
return return_value

* Optional: set a ``base`` converter (``base`` expects a class name) to build on top of an existing converter, e.g.
for the built-in ones: ``DictCoverter``, ``NumpyArrayConverter``, ``PandasDataFrameConverter``, ``PandasSeriesConverter``
* Optional: register the converter: you can **(a)** register a type so that your converter becomes the default for
this type during write operations and/or **(b)** you can register an alias that will allow you to explicitly call
your converter by name instead of just by class name

The following examples should make it much easier to follow - it defines a DataFrame converter that extends the
built-in DataFrame converter to add support for dropping nan's::

from xlwings.conversion import Converter, PandasDataFrameConverter

class DataFrameDropna(Converter):

base = PandasDataFrameConverter

@staticmethod
def read_value(builtin_df, options):
dropna = options.get('dropna', False) # set default to False
if dropna:
converted_df = builtin_df.dropna()
else:
converted_df = builtin_df
# This will arrive in Python when using the DataFrameDropna converter for reading
return converted_df

@staticmethod
def write_value(df, options):
dropna = options.get('dropna', False)
if dropna:
converted_df = df.dropna()
else:
converted_df = df
# This will be passed to the built-in PandasDataFrameConverter when writing
return converted_df


Now let's see how the different converters can be applied::

# Fire up a Workbook and create a sample DataFrame
wb = Workbook()
df = pd.DataFrame([[1.,10.],[2.,np.nan], [3., 30.]])

* Default converter for DataFrames::

# Write
Range('A1').value = df

# Read
Range('A1:C4').options(pd.DataFrame).value

* DataFrameDropna converter::

# Write
Range('A7').options(DataFrameDropna, dropna=True).value = df

# Read
Range('A1:C4').options(DataFrameDropna, dropna=True).value

* Register an alias (optional)::

DataFrameDropna.register('df_dropna')

# Write
Range('A12').options('df_dropna', dropna=True).value = df

# Read
Range('A1:C4').options('df_dropna', dropna=True).value


* Register DataFrameDropna as default converter for DataFrames (optional)::

DataFrameDropna.register(pd.DataFrame)

# Write
Range('A13').options(dropna=True).value = df

# Read
Range('A1:C4').options(pd.DataFrame, dropna=True).value

These samples all work the same with UDFs, e.g.::

@xw.func
@arg('x', DataFrameDropna, dropna=True)
@ret(DataFrameDropna, dropna=True)
def myfunction(x):
# ...
return x


.. note::
Python objects run through multiple stages of a transformation pipeline when they are being written to Excel. The
same holds true in the other direction, when Excel/COM objects are being read into Python.

Pipelines are internally defined by ``Accessor`` classes. A Converter is just a special Accessor which
converts to/from a particular type by adding an extra stage to the pipeline of the default Accessor. For example, the
``PandasDataFrameConverter`` defines how a list of list (as delivered by the default Accessor) should be turned
into a Pandas DataFrame.

The ``Converter`` class provides basic scaffolding to make the task of writing a new Converter easier. If
you need more control you can subclass ``Accessor`` directly, but this part requires more work and is currently
undocumented.
10 changes: 6 additions & 4 deletions docs/debugging.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ To begin with, Excel will show Python errors in a Message Box:
.. note:: On Mac, if the ``import`` of a module/package fails before xlwings is imported, the popup will not be shown and the StatusBar
will not be reset. However, the error will still be logged in the log file. For the location of the logfile, see :ref:`log`.

**RunPython**
RunPython
---------

Consider the following code structure of your Python source code:

Expand Down Expand Up @@ -47,17 +48,18 @@ source code:
RunPython ("import my_module; my_module.my_macro()")
End Sub

**UDF debug server**
UDF debug server
----------------

Windows only: To debug UDFs, just set ``UDF_DEBUG_SERVER = True`` in the VBA Settings, at the top of the xlwings VBA module.
Then add the following lines at the end of your Python source file and run it. Depending on which IDE you use, you
might want to run things in "debug" mode (e.g. in case your using PyCharm)::
might want to run things in "debug" mode (e.g. in case your using PyCharm or PyDev)::


if __name__ == '__main__':
xw.serve()

When you recalculate the Sheet (``Ctrl-Alt-F9``), the code will stop at breakpoints or print any statements that you
When you recalculate the Sheet (``Ctrl-Alt-F9``), the code will stop at breakpoints or output any print calls that you
may have.

The following screenshot shows the code stopped at a breakpoint in the community version of PyCharm:
Expand Down
Binary file added docs/images/mpl_basic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/mpl_udf.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ on **Windows** and **Mac**.
datastructures
vba
debugging
matplotlib
udfs
converters
command_line
Expand Down
1 change: 1 addition & 0 deletions docs/index_latex.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
datastructures
vba
debugging
matplotlib
udfs
converters
command_line
Expand Down
Loading

0 comments on commit 83b3d91

Please sign in to comment.