Skip to content

Commit 182ffc4

Browse files
inklesspennitzmahone
authored andcommitted
Allow writing generated code to a file-like object. (#115)
This allows a caller to better control when and where the generated code is written, for issue #47. (cherry picked from commit 9e9dffb)
1 parent 74731f9 commit 182ffc4

File tree

3 files changed

+30
-1
lines changed

3 files changed

+30
-1
lines changed

doc/source/cdef.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,12 @@ write). If you choose, you can include this .py file pre-packaged in
667667
your own distributions: it is identical for any Python version (2 or
668668
3).
669669

670+
*New in version 1.17.1:* ``filename`` can instead be a file-like object
671+
(such as a StringIO instance). The generated code will be written to this
672+
file-like object. However, if an error arises during generation, partial
673+
code may be written; it is the caller's responsibility to clean up
674+
if this occurs.
675+
670676
**ffibuilder.emit_c_code(filename):** generate the given .c file (for API
671677
mode) without compiling it. Can be used if you have some other method
672678
to compile it, e.g. if you want to integrate with some larger build
@@ -676,6 +682,12 @@ platform, the .c file itself is generic (it would be exactly the same
676682
if produced on a different OS, with a different version of CPython, or
677683
with PyPy; it is done with generating the appropriate ``#ifdef``).
678684

685+
*New in version 1.17.1:* ``filename`` can instead be a file-like object
686+
(such as a StringIO instance). The generated code will be written to this
687+
file-like object. However, if an error arises during generation, partial
688+
code may be written; it is the caller's responsibility to clean up
689+
if this occurs.
690+
679691
**ffibuilder.distutils_extension(tmpdir='build', verbose=True):** for
680692
distutils-based ``setup.py`` files. Calling this creates the .c file
681693
if needed in the given ``tmpdir``, and returns a

src/cffi/recompiler.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,13 +1419,20 @@ def write(self, s):
14191419
s = s.encode('ascii')
14201420
super(NativeIO, self).write(s)
14211421

1422+
def _is_file_like(maybefile):
1423+
# compare to xml.etree.ElementTree._get_writer
1424+
return hasattr(maybefile, 'write')
1425+
14221426
def _make_c_or_py_source(ffi, module_name, preamble, target_file, verbose):
14231427
if verbose:
14241428
print("generating %s" % (target_file,))
14251429
recompiler = Recompiler(ffi, module_name,
14261430
target_is_python=(preamble is None))
14271431
recompiler.collect_type_table()
14281432
recompiler.collect_step_tables()
1433+
if _is_file_like(target_file):
1434+
recompiler.write_source_to_f(target_file, preamble)
1435+
return True
14291436
f = NativeIO()
14301437
recompiler.write_source_to_f(f, preamble)
14311438
output = f.getvalue()
@@ -1525,6 +1532,9 @@ def recompile(ffi, module_name, preamble, tmpdir='.', call_c_compiler=True,
15251532
if ffi._windows_unicode:
15261533
ffi._apply_windows_unicode(kwds)
15271534
if preamble is not None:
1535+
if call_c_compiler and _is_file_like(c_file):
1536+
raise TypeError("Writing to file-like objects is not supported "
1537+
"with call_c_compiler=True")
15281538
embedding = (ffi._embedding is not None)
15291539
if embedding:
15301540
ffi._apply_embedding_fix(kwds)

testing/cffi1/test_new_ffi_1.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import cffi
55
from testing.udir import udir
66
from testing.support import *
7-
from cffi.recompiler import recompile
7+
from cffi.recompiler import recompile, NativeIO
88
from cffi.cffi_opcode import PRIMITIVE_TO_INDEX
99

1010
SIZE_OF_INT = ctypes.sizeof(ctypes.c_int)
@@ -1776,6 +1776,13 @@ def test_emit_c_code(self):
17761776
ffi.emit_c_code(c_file)
17771777
assert os.path.isfile(c_file)
17781778

1779+
def test_emit_c_code_to_file_obj(self):
1780+
ffi = cffi.FFI()
1781+
ffi.set_source("foobar", "??")
1782+
fileobj = NativeIO()
1783+
ffi.emit_c_code(fileobj)
1784+
assert 'foobar' in fileobj.getvalue()
1785+
17791786
def test_import_from_lib(self):
17801787
ffi2 = cffi.FFI()
17811788
ffi2.cdef("int myfunc(int); extern int myvar;\n#define MYFOO ...\n")

0 commit comments

Comments
 (0)