summaryrefslogtreecommitdiff
path: root/README.rst
blob: 20d04bdd608941af1201a4b4a77b66e616aaf767 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
cffi-example: an example project showing how to use Python's CFFI
=================================================================

A complete project which demonstrates how to use CFFI 1.0 during development of
a modern Python package:

* Development
* Testing across Python interpreters (Py2, Py3, PyPy)
* Packaging and distribution


Status
------

This repository is the result of a weeks worth of frustrated googling. I don't
think anything is *too* misleading, but I would deeply appreciate feedback from
anyone who actually knows what's going on.


Examples
--------

* ``cffi_example/person.py`` and ``cffi_example/build_person.py`` is an example
  of a Python class which wraps a C class, including proper memory management
  and passing around string buffers::

    >>> p = Person(u"Alex", u"Smith", 72) # --> calls person_create(...)
    >>> p.get_full_name() # --> calls person_get_full_name(p, buf, len(buf))
    u"Alex Smith"

* ``cffi_example/fnmatch.py`` and ``cffi_example/build_fnmatch.py`` is an
  example of wrapping a shared library (in this case ``libc``'s `fnmatch`__)::

    >>> fnmatch("f*", "foo") # --> calls libc's fnmatch
    True

__ http://man7.org/linux/man-pages/man3/fnmatch.3.html


Development
-----------

1. Clone the repository::

    $ git clone git@github.com:wolever/python-cffi-example.git

2. Make sure that ``cffi``, ``py.test``, and ``tox`` are installed::

    $ pip install -r requirements-testing.txt

3. Run ``python setup.py develop`` to build development versions of the
   modules::

    $ python setup.py develop
    ...
    Finished processing dependencies for cffi-example==0.1

4. Test locally with ``py.test``::

    $ py.test test/
    =========================== test session starts ===========================
    platform darwin -- Python 2.7.2 -- py-1.4.28 -- pytest-2.7.1
    rootdir: /Users/wolever/code/python-cffi-example, inifile:
    collected 7 items

    test/test_fnmatch.py ....
    test/test_person.py ...

    ======================== 7 passed in 0.03 seconds =========================

5. Test against multiple Python environments using ``tox``::

    $ tox
    ...
    _________________________________ summary _________________________________
    py26: commands succeeded
    py27: commands succeeded
    py33: commands succeeded
    pypy: commands succeeded
    congratulations :)

6. I prefer to use ``make`` to clean and rebuild libraries during development
   (since it's faster than ``setup.py develop``)::

    $ make clean
    ...
    $ make
    python cffi_example/build_person.py
    python cffi_example/build_fnmatch.py


Packaging
---------

This example uses `CFFI's recommended combination`__ of ``setuptools`` and
``cffi_modules``::

    $ cat setup.py
    from setuptools import setup

    setup(
        ...
        install_requires=["cffi>=1.0.0"],
        setup_requires=["cffi>=1.0.0"],
        cffi_modules=[
            "./cffi_example/build_person.py:ffi",
            "./cffi_example/build_fnmatch.py:ffi",
        ],
    )

__ https://cffi.readthedocs.org/en/latest/cdef.html#distutils-setuptools

This will cause the modules to be built with ``setup.py develop`` or ``setup.py
build``, and installed with ``setup.py install``.

**Note**: Many examples you'll see online use either the ``distutils``
``ext_modules=[cffi_example.ffi.distribute_extension()]`` method, or the
more complex ``keywords_with_side_effects`` method. To the best of my
knowledge these methods are only necessary with CFFI < 1.0 or ``distutils``.
`dstufft`__ has written a fantastic post —
https://caremad.io/2014/11/distributing-a-cffi-project/ — which details the
drawbacks of the ``ext_modules`` method and explains the
``keywords_with_side_effects`` method, but I believe it was written before CFFI
1.0 so it does not include the now preferred ``cffi_modules`` method.

__ https://twitter.com/dstufft/


Distribution
------------

Distribution is just like any other Python package, with the obvious caveat
that wheels will be platform-specific::

    $ python setup.py sdist bdist_wheel
    ...
    $ ls dist/
    cffi-example-0.1.tar.gz
    cffi_example-0.1-cp27-none-macosx_10_8_intel.whl

And the package can be uploaded to PyPI using ``upload``::

    $ python setup.py sdist upload


Note that users of the source package will need to have ``cffi`` (and a C
compiler, and development headers of any libraries you're linking against)
installed to build and install your package.

Note also that the ``MANIFEST.in`` file will need to be updated to include any
new source or headers you may add during development. The ``tox`` tests will
catch this error, but it may not be obvious how to correct it.


Caveats
-------

* Doesn't yet cover using ``dlopen(...)`` to dynamically load ``.so`` files
  because I haven't figured out any best practices for building custom shared
  libraries along with a Python package's lifecycle, and the
  `CFFI documentation on loading dynamic libraries`__ covers the details of
  making the ``lib.dlopen(...)`` call.

* Using ``make`` to build modules during development is less than ideal. Please
  post here if there's a better way to do this:
  http://stackoverflow.com/q/30823397/71522


__ https://cffi.readthedocs.org/en/latest/overview.html#out-of-line-abi-level