Skip to content

Commit e893f43

Browse files
committed
Merge pull request #5403 from jtratner/remove-excel-metaclass
CLN: Remove unnecessary ExcelWriterMeta metaclass
2 parents ec34165 + b4920d2 commit e893f43

File tree

1 file changed

+59
-60
lines changed

1 file changed

+59
-60
lines changed

pandas/io/excel.py

+59-60
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
_writer_extensions = ["xlsx", "xls", "xlsm"]
2525
_writers = {}
2626

27+
2728
def register_writer(klass):
2829
"""Adds engine to the excel writer registry. You must use this method to
2930
integrate with ``to_excel``. Also adds config options for any new
@@ -40,12 +41,14 @@ def register_writer(klass):
4041
engine_name, validator=str)
4142
_writer_extensions.append(ext)
4243

44+
4345
def get_writer(engine_name):
4446
try:
4547
return _writers[engine_name]
4648
except KeyError:
4749
raise ValueError("No Excel writer '%s'" % engine_name)
4850

51+
4952
def read_excel(io, sheetname, **kwds):
5053
"""Read an Excel table into a pandas DataFrame
5154
@@ -80,7 +83,7 @@ def read_excel(io, sheetname, **kwds):
8083
engine: string, default None
8184
If io is not a buffer or path, this must be set to identify io.
8285
Acceptable values are None or xlrd
83-
86+
8487
Returns
8588
-------
8689
parsed : DataFrame
@@ -90,9 +93,9 @@ def read_excel(io, sheetname, **kwds):
9093
kwds.pop('kind')
9194
warn("kind keyword is no longer supported in read_excel and may be "
9295
"removed in a future version", FutureWarning)
93-
94-
engine = kwds.pop('engine', None)
95-
96+
97+
engine = kwds.pop('engine', None)
98+
9699
return ExcelFile(io, engine=engine).parse(sheetname=sheetname, **kwds)
97100

98101

@@ -119,9 +122,9 @@ def __init__(self, io, **kwds):
119122
"support, current version " + xlrd.__VERSION__)
120123

121124
self.io = io
122-
125+
123126
engine = kwds.pop('engine', None)
124-
127+
125128
if engine is not None and engine != 'xlrd':
126129
raise ValueError("Unknown engine: %s" % engine)
127130

@@ -133,7 +136,8 @@ def __init__(self, io, **kwds):
133136
data = io.read()
134137
self.book = xlrd.open_workbook(file_contents=data)
135138
else:
136-
raise ValueError('Must explicitly set engine if not passing in buffer or path for io.')
139+
raise ValueError('Must explicitly set engine if not passing in'
140+
' buffer or path for io.')
137141

138142
def parse(self, sheetname, header=0, skiprows=None, skip_footer=0,
139143
index_col=None, parse_cols=None, parse_dates=False,
@@ -291,13 +295,15 @@ def __enter__(self):
291295
def __exit__(self, exc_type, exc_value, traceback):
292296
self.close()
293297

298+
294299
def _trim_excel_header(row):
295300
# trim header row so auto-index inference works
296301
# xlrd uses '' , openpyxl None
297302
while len(row) > 0 and (row[0] == '' or row[0] is None):
298303
row = row[1:]
299304
return row
300305

306+
301307
def _conv_value(val):
302308
# Convert numpy types to Python types for the Excel writers.
303309
if com.is_integer(val):
@@ -312,34 +318,45 @@ def _conv_value(val):
312318
return val
313319

314320

315-
class ExcelWriterMeta(abc.ABCMeta):
321+
@add_metaclass(abc.ABCMeta)
322+
class ExcelWriter(object):
316323
"""
317-
Metaclass that dynamically chooses the ExcelWriter to use.
318-
319-
If you directly instantiate a subclass, it skips the engine lookup.
320-
321-
Defining an ExcelWriter implementation (see abstract methods on ExcelWriter for more...).
322-
323-
- Mandatory (but not checked at run time):
324-
- ``write_cells(self, cells, sheet_name=None, startrow=0, startcol=0)``
325-
--> called to write additional DataFrames to disk
326-
- ``supported_extensions`` (tuple of supported extensions), used to check
327-
that engine supports the given extension.
328-
- ``engine`` - string that gives the engine name. Necessary to
329-
instantiate class directly and bypass ``ExcelWriterMeta`` engine lookup.
330-
- ``save(self)`` --> called to save file to disk
331-
- Optional:
332-
- ``__init__(self, path, **kwargs)`` --> always called with path as first
333-
argument.
324+
Class for writing DataFrame objects into excel sheets, default is to use
325+
xlwt for xls, openpyxl for xlsx. See DataFrame.to_excel for typical usage.
334326
335-
You also need to register the class with ``register_writer()``.
327+
Parameters
328+
----------
329+
path : string
330+
Path to xls or xlsx file.
331+
engine : string (optional)
332+
Engine to use for writing. If None, defaults to
333+
``io.excel.<extension>.writer``. NOTE: can only be passed as a keyword
334+
argument.
336335
"""
337-
338-
def __call__(cls, path, **kwargs):
339-
engine = kwargs.pop('engine', None)
340-
# if it's not an ExcelWriter baseclass, dont' do anything (you've
341-
# probably made an explicit choice here)
342-
if not isinstance(getattr(cls, 'engine', None), compat.string_types):
336+
# Defining an ExcelWriter implementation (see abstract methods for more...)
337+
338+
# - Mandatory
339+
# - ``write_cells(self, cells, sheet_name=None, startrow=0, startcol=0)``
340+
# --> called to write additional DataFrames to disk
341+
# - ``supported_extensions`` (tuple of supported extensions), used to
342+
# check that engine supports the given extension.
343+
# - ``engine`` - string that gives the engine name. Necessary to
344+
# instantiate class directly and bypass ``ExcelWriterMeta`` engine
345+
# lookup.
346+
# - ``save(self)`` --> called to save file to disk
347+
# - Mostly mandatory (i.e. should at least exist)
348+
# - book, cur_sheet, path
349+
350+
# - Optional:
351+
# - ``__init__(self, path, engine=None, **kwargs)`` --> always called
352+
# with path as first argument.
353+
354+
# You also need to register the class with ``register_writer()``.
355+
# Technically, ExcelWriter implementations don't need to subclass
356+
# ExcelWriter.
357+
def __new__(cls, path, engine=None, **kwargs):
358+
# only switch class if generic(ExcelWriter)
359+
if cls == ExcelWriter:
343360
if engine is None:
344361
ext = os.path.splitext(path)[-1][1:]
345362
try:
@@ -348,31 +365,14 @@ def __call__(cls, path, **kwargs):
348365
error = ValueError("No engine for filetype: '%s'" % ext)
349366
raise error
350367
cls = get_writer(engine)
351-
writer = cls.__new__(cls, path, **kwargs)
352-
writer.__init__(path, **kwargs)
353-
return writer
354-
355368

356-
@add_metaclass(ExcelWriterMeta)
357-
class ExcelWriter(object):
358-
"""
359-
Class for writing DataFrame objects into excel sheets, default is to use
360-
xlwt for xls, openpyxl for xlsx. See DataFrame.to_excel for typical usage.
369+
return object.__new__(cls)
361370

362-
Parameters
363-
----------
364-
path : string
365-
Path to xls or xlsx file.
366-
engine : string (optional)
367-
Engine to use for writing. If None, defaults to ``io.excel.<extension>.writer``.
368-
NOTE: can only be passed as a keyword argument.
369-
"""
370371
# declare external properties you can count on
371372
book = None
372373
curr_sheet = None
373374
path = None
374375

375-
376376
@abc.abstractproperty
377377
def supported_extensions(self):
378378
"extensions that writer engine supports"
@@ -407,9 +407,6 @@ def save(self):
407407
pass
408408

409409
def __init__(self, path, engine=None, **engine_kwargs):
410-
# note that subclasses will *never* get anything for engine
411-
# included here so that it's visible as part of the public signature.
412-
413410
# validate that this engine can handle the extnesion
414411
ext = os.path.splitext(path)[-1]
415412
self.check_extension(ext)
@@ -455,7 +452,7 @@ class _OpenpyxlWriter(ExcelWriter):
455452
engine = 'openpyxl'
456453
supported_extensions = ('.xlsx', '.xlsm')
457454

458-
def __init__(self, path, **engine_kwargs):
455+
def __init__(self, path, engine=None, **engine_kwargs):
459456
# Use the openpyxl module as the Excel writer.
460457
from openpyxl.workbook import Workbook
461458

@@ -511,6 +508,7 @@ def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0):
511508
startrow + cell.row + 1,
512509
cletterend,
513510
startrow + cell.mergestart + 1))
511+
514512
@classmethod
515513
def _convert_to_style(cls, style_dict):
516514
"""
@@ -539,7 +537,7 @@ class _XlwtWriter(ExcelWriter):
539537
engine = 'xlwt'
540538
supported_extensions = ('.xls',)
541539

542-
def __init__(self, path, **engine_kwargs):
540+
def __init__(self, path, engine=None, **engine_kwargs):
543541
# Use the xlwt module as the Excel writer.
544542
import xlwt
545543

@@ -599,7 +597,8 @@ def write_cells(self, cells, sheet_name=None, startrow=0, startcol=0):
599597
val, style)
600598

601599
@classmethod
602-
def _style_to_xlwt(cls, item, firstlevel=True, field_sep=',', line_sep=';'):
600+
def _style_to_xlwt(cls, item, firstlevel=True, field_sep=',',
601+
line_sep=';'):
603602
"""helper which recursively generate an xlwt easy style string
604603
for example:
605604
@@ -617,12 +616,12 @@ def _style_to_xlwt(cls, item, firstlevel=True, field_sep=',', line_sep=';'):
617616
if hasattr(item, 'items'):
618617
if firstlevel:
619618
it = ["%s: %s" % (key, cls._style_to_xlwt(value, False))
620-
for key, value in item.items()]
619+
for key, value in item.items()]
621620
out = "%s " % (line_sep).join(it)
622621
return out
623622
else:
624623
it = ["%s %s" % (key, cls._style_to_xlwt(value, False))
625-
for key, value in item.items()]
624+
for key, value in item.items()]
626625
out = "%s " % (field_sep).join(it)
627626
return out
628627
else:
@@ -659,11 +658,11 @@ class _XlsxWriter(ExcelWriter):
659658
engine = 'xlsxwriter'
660659
supported_extensions = ('.xlsx',)
661660

662-
def __init__(self, path, **engine_kwargs):
661+
def __init__(self, path, engine=None, **engine_kwargs):
663662
# Use the xlsxwriter module as the Excel writer.
664663
import xlsxwriter
665664

666-
super(_XlsxWriter, self).__init__(path, **engine_kwargs)
665+
super(_XlsxWriter, self).__init__(path, engine=engine, **engine_kwargs)
667666

668667
self.book = xlsxwriter.Workbook(path, **engine_kwargs)
669668

0 commit comments

Comments
 (0)