mirror of
https://github.com/kennethreitz/tablib.git
synced 2026-06-05 06:56:13 +00:00
Drop python 2 support
Remove support python 2 from doc, requirements.txt and config Replace unicode with str Remove dbfpy folder and rename dbfpy3 to dbfpy Remove compat file and remove python2 packages from dependency
This commit is contained in:
committed by
Claude Paroz
parent
44f43516a5
commit
b3c7145c40
@@ -1,6 +1,5 @@
|
||||
language: python
|
||||
python:
|
||||
- 2.7
|
||||
- 3.6
|
||||
- 3.7
|
||||
- 3.8
|
||||
|
||||
@@ -24,6 +24,7 @@ Here is a list of passed and present much-appreciated contributors:
|
||||
Mark Walling
|
||||
Mathias Loesch
|
||||
Mike Waldner
|
||||
Peyman Salehi
|
||||
Rabin Nankhwa
|
||||
Tommy Anthony
|
||||
Tsuyoshi Hombashi
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# History
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- Dropped Python 2 support
|
||||
|
||||
## 0.14.0 (2019-10-19)
|
||||
|
||||
### Deprecations
|
||||
|
||||
+2
-1
@@ -74,6 +74,7 @@ THE SOFTWARE.
|
||||
Pythons Supported
|
||||
-----------------
|
||||
|
||||
Python 2.7 and 3.5+ are officially supported.
|
||||
Python 3.5+ are officially supported.
|
||||
|
||||
Now, go :ref:`install Tablib <install>`.
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import re
|
||||
@@ -11,7 +10,6 @@ from setuptools import find_packages, setup
|
||||
install = [
|
||||
'odfpy',
|
||||
'openpyxl>=2.4.0',
|
||||
'backports.csv;python_version<"3.0"',
|
||||
'markuppy',
|
||||
'xlrd',
|
||||
'xlwt',
|
||||
@@ -41,15 +39,14 @@ setup(
|
||||
'Natural Language :: English',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3 :: Only',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
],
|
||||
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',
|
||||
python_requires='>=3.5',
|
||||
install_requires=install,
|
||||
extras_require={
|
||||
'pandas': ['pandas'],
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
tablib.compat
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Tablib compatibility module.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
is_py3 = (sys.version_info[0] > 2)
|
||||
|
||||
|
||||
if is_py3:
|
||||
from io import StringIO
|
||||
from statistics import median
|
||||
from itertools import zip_longest as izip_longest
|
||||
import csv
|
||||
import tablib.packages.dbfpy3 as dbfpy
|
||||
|
||||
unicode = str
|
||||
xrange = range
|
||||
|
||||
else:
|
||||
from StringIO import StringIO
|
||||
from tablib.packages.statistics import median
|
||||
from itertools import izip_longest
|
||||
from backports import csv
|
||||
import tablib.packages.dbfpy as dbfpy
|
||||
|
||||
unicode = unicode
|
||||
xrange = xrange
|
||||
|
||||
from MarkupPy import markup # Kept temporarily to avoid breaking existing imports
|
||||
+12
-18
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
tablib.core
|
||||
~~~~~~~~~~~
|
||||
@@ -15,8 +14,6 @@ from operator import itemgetter
|
||||
|
||||
from tablib import formats
|
||||
|
||||
from tablib.compat import unicode
|
||||
|
||||
|
||||
__title__ = 'tablib'
|
||||
__author__ = 'Kenneth Reitz'
|
||||
@@ -25,7 +22,7 @@ __copyright__ = 'Copyright 2017 Kenneth Reitz. 2019 Jazzband.'
|
||||
__docformat__ = 'restructuredtext'
|
||||
|
||||
|
||||
class Row(object):
|
||||
class Row:
|
||||
"""Internal Row object. Mainly used for filtering."""
|
||||
|
||||
__slots__ = ['_row', 'tags']
|
||||
@@ -104,7 +101,7 @@ class Row(object):
|
||||
return bool(len(set(tag) & set(self.tags)))
|
||||
|
||||
|
||||
class Dataset(object):
|
||||
class Dataset:
|
||||
"""The :class:`Dataset` object is the heart of Tablib. It provides all core
|
||||
functionality.
|
||||
|
||||
@@ -173,7 +170,7 @@ class Dataset(object):
|
||||
return self.height
|
||||
|
||||
def __getitem__(self, key):
|
||||
if isinstance(key, (str, unicode)):
|
||||
if isinstance(key, str):
|
||||
if key in self.headers:
|
||||
pos = self.headers.index(key) # get 'key' index from each data
|
||||
return [row[pos] for row in self._data]
|
||||
@@ -191,7 +188,7 @@ class Dataset(object):
|
||||
self._data[key] = Row(value)
|
||||
|
||||
def __delitem__(self, key):
|
||||
if isinstance(key, (str, unicode)):
|
||||
if isinstance(key, str):
|
||||
|
||||
if key in self.headers:
|
||||
|
||||
@@ -213,15 +210,15 @@ class Dataset(object):
|
||||
except AttributeError:
|
||||
return '<dataset object>'
|
||||
|
||||
def __unicode__(self):
|
||||
def __str__(self):
|
||||
result = []
|
||||
|
||||
# Add unicode representation of headers.
|
||||
# Add str representation of headers.
|
||||
if self.__headers:
|
||||
result.append([unicode(h) for h in self.__headers])
|
||||
result.append([str(h) for h in self.__headers])
|
||||
|
||||
# Add unicode representation of rows.
|
||||
result.extend(list(map(unicode, row)) for row in self._data)
|
||||
# Add str representation of rows.
|
||||
result.extend(list(map(str, row)) for row in self._data)
|
||||
|
||||
lens = [list(map(len, row)) for row in result]
|
||||
field_lens = list(map(max, zip(*lens)))
|
||||
@@ -234,9 +231,6 @@ class Dataset(object):
|
||||
|
||||
return '\n'.join(format_string.format(*row) for row in result)
|
||||
|
||||
def __str__(self):
|
||||
return self.__unicode__()
|
||||
|
||||
# ---------
|
||||
# Internals
|
||||
# ---------
|
||||
@@ -824,7 +818,7 @@ class Dataset(object):
|
||||
each cell value.
|
||||
"""
|
||||
|
||||
if isinstance(col, unicode):
|
||||
if isinstance(col, str):
|
||||
if col in self.headers:
|
||||
col = self.headers.index(col) # get 'key' index from each data
|
||||
else:
|
||||
@@ -855,7 +849,7 @@ class Dataset(object):
|
||||
sorted.
|
||||
"""
|
||||
|
||||
if isinstance(col, (str, unicode)):
|
||||
if isinstance(col, str):
|
||||
|
||||
if not self.headers:
|
||||
raise HeadersNeeded
|
||||
@@ -1017,7 +1011,7 @@ class Dataset(object):
|
||||
return _dset
|
||||
|
||||
|
||||
class Databook(object):
|
||||
class Databook:
|
||||
"""A book of :class:`Dataset` objects.
|
||||
"""
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Tablib - formats
|
||||
"""
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Tablib - *SV Support.
|
||||
"""
|
||||
|
||||
from tablib.compat import csv, StringIO, unicode
|
||||
import csv
|
||||
from io import StringIO
|
||||
|
||||
|
||||
title = 'csv'
|
||||
extensions = ('csv',)
|
||||
|
||||
|
||||
DEFAULT_DELIMITER = unicode(',')
|
||||
DEFAULT_DELIMITER = ','
|
||||
|
||||
|
||||
def export_stream_set(dataset, **kwargs):
|
||||
|
||||
+11
-31
@@ -1,24 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Tablib - DBF Support.
|
||||
"""
|
||||
import tempfile
|
||||
import struct
|
||||
import io
|
||||
import os
|
||||
import struct
|
||||
import tempfile
|
||||
|
||||
from tablib.compat import StringIO
|
||||
from tablib.compat import dbfpy
|
||||
from tablib.compat import is_py3
|
||||
|
||||
if is_py3:
|
||||
from tablib.packages.dbfpy3 import dbf
|
||||
from tablib.packages.dbfpy3 import dbfnew
|
||||
from tablib.packages.dbfpy3 import record as dbfrecord
|
||||
import io
|
||||
else:
|
||||
from tablib.packages.dbfpy import dbf
|
||||
from tablib.packages.dbfpy import dbfnew
|
||||
from tablib.packages.dbfpy import record as dbfrecord
|
||||
from tablib.packages.dbfpy import dbf
|
||||
from tablib.packages.dbfpy import dbfnew
|
||||
from tablib.packages.dbfpy import record as dbfrecord
|
||||
|
||||
|
||||
title = 'dbf'
|
||||
@@ -50,10 +39,7 @@ def export_set(dataset):
|
||||
|
||||
dbf_file.close()
|
||||
dbf_stream = open(temp_uri, 'rb')
|
||||
if is_py3:
|
||||
stream = io.BytesIO(dbf_stream.read())
|
||||
else:
|
||||
stream = StringIO(dbf_stream.read())
|
||||
stream = io.BytesIO(dbf_stream.read())
|
||||
dbf_stream.close()
|
||||
os.close(temp_file)
|
||||
os.remove(temp_uri)
|
||||
@@ -63,10 +49,7 @@ def import_set(dset, in_stream, headers=True):
|
||||
"""Returns a dataset from a DBF stream."""
|
||||
|
||||
dset.wipe()
|
||||
if is_py3:
|
||||
_dbf = dbf.Dbf(io.BytesIO(in_stream))
|
||||
else:
|
||||
_dbf = dbf.Dbf(StringIO(in_stream))
|
||||
_dbf = dbf.Dbf(io.BytesIO(in_stream))
|
||||
dset.headers = _dbf.fieldNames
|
||||
for record in range(_dbf.recordCount):
|
||||
row = [_dbf[record][f] for f in _dbf.fieldNames]
|
||||
@@ -76,12 +59,9 @@ def detect(stream):
|
||||
"""Returns True if the given stream is valid DBF"""
|
||||
#_dbf = dbf.Table(StringIO(stream))
|
||||
try:
|
||||
if is_py3:
|
||||
if type(stream) is not bytes:
|
||||
stream = bytes(stream, 'utf-8')
|
||||
_dbf = dbf.Dbf(io.BytesIO(stream), readOnly=True)
|
||||
else:
|
||||
_dbf = dbf.Dbf(StringIO(stream), readOnly=True)
|
||||
if type(stream) is not bytes:
|
||||
stream = bytes(stream, 'utf-8')
|
||||
_dbf = dbf.Dbf(io.BytesIO(stream), readOnly=True)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@@ -11,7 +11,6 @@ except ImportError:
|
||||
|
||||
import tablib
|
||||
|
||||
from tablib.compat import unicode
|
||||
|
||||
title = 'df'
|
||||
extensions = ('df', )
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Tablib - HTML export support.
|
||||
"""
|
||||
|
||||
@@ -9,7 +7,6 @@ from io import BytesIO
|
||||
|
||||
from MarkupPy import markup
|
||||
import tablib
|
||||
from tablib.compat import unicode
|
||||
|
||||
BOOK_ENDINGS = 'h3'
|
||||
|
||||
@@ -43,7 +40,7 @@ def export_set(dataset):
|
||||
|
||||
# Allow unicode characters in output
|
||||
wrapper = codecs.getwriter("utf8")(stream)
|
||||
wrapper.writelines(unicode(page))
|
||||
wrapper.writelines(str(page))
|
||||
|
||||
return stream.getvalue().decode('utf-8')
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Tablib - Jira table export support.
|
||||
|
||||
Generates a Jira table from the dataset.
|
||||
"""
|
||||
from tablib.compat import unicode
|
||||
|
||||
title = 'jira'
|
||||
|
||||
@@ -35,5 +32,5 @@ def _get_header(headers):
|
||||
|
||||
def _serialize_row(row, delimiter='|'):
|
||||
return '%s%s%s' % (delimiter,
|
||||
delimiter.join([unicode(item) if item else ' ' for item in row]),
|
||||
delimiter.join([str(item) if item else ' ' for item in row]),
|
||||
delimiter)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Tablib - JSON Support
|
||||
"""
|
||||
import decimal
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Tablib - LaTeX table export support.
|
||||
|
||||
Generates a LaTeX booktabs-style table from the dataset.
|
||||
"""
|
||||
import re
|
||||
|
||||
from tablib.compat import unicode
|
||||
|
||||
title = 'latex'
|
||||
extensions = ('tex',)
|
||||
@@ -119,7 +116,7 @@ def _serialize_row(row):
|
||||
:param row: single dataset row
|
||||
"""
|
||||
|
||||
new_row = [_escape_tex_reserved_symbols(unicode(item)) if item else '' for
|
||||
new_row = [_escape_tex_reserved_symbols(str(item)) if item else '' for
|
||||
item in row]
|
||||
return 6 * ' ' + ' & '.join(new_row) + ' \\\\'
|
||||
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Tablib - ODF Support.
|
||||
"""
|
||||
|
||||
from io import BytesIO
|
||||
from odf import opendocument, style, table, text
|
||||
from tablib.compat import unicode
|
||||
|
||||
title = 'ods'
|
||||
extensions = ('ods',)
|
||||
@@ -57,9 +54,9 @@ def dset_sheet(dataset, ws):
|
||||
odf_row = table.TableRow(stylename=bold, defaultcellstylename='bold')
|
||||
for j, col in enumerate(row):
|
||||
try:
|
||||
col = unicode(col, errors='ignore')
|
||||
col = str(col, errors='ignore')
|
||||
except TypeError:
|
||||
## col is already unicode
|
||||
## col is already str
|
||||
pass
|
||||
ws.addElement(table.TableColumn())
|
||||
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Tablib - reStructuredText Support
|
||||
"""
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from itertools import zip_longest
|
||||
from statistics import median
|
||||
from textwrap import TextWrapper
|
||||
|
||||
from tablib.compat import (
|
||||
median,
|
||||
unicode,
|
||||
izip_longest,
|
||||
)
|
||||
|
||||
|
||||
title = 'rst'
|
||||
extensions = ('rst',)
|
||||
@@ -28,10 +19,10 @@ JUSTIFY_RIGHT = 'right'
|
||||
JUSTIFY_VALUES = (JUSTIFY_LEFT, JUSTIFY_CENTER, JUSTIFY_RIGHT)
|
||||
|
||||
|
||||
def to_unicode(value):
|
||||
def to_str(value):
|
||||
if isinstance(value, bytes):
|
||||
return value.decode('utf-8')
|
||||
return unicode(value)
|
||||
return str(value)
|
||||
|
||||
|
||||
def _max_word_len(text):
|
||||
@@ -60,7 +51,7 @@ def _get_column_string_lengths(dataset):
|
||||
for row in dataset.dict:
|
||||
values = iter(row.values() if hasattr(row, 'values') else row)
|
||||
for i, val in enumerate(values):
|
||||
text = to_unicode(val)
|
||||
text = to_str(val)
|
||||
column_lengths[i].append(len(text))
|
||||
word_lens[i] = max(word_lens[i], _max_word_len(text))
|
||||
return column_lengths, word_lens
|
||||
@@ -86,10 +77,10 @@ def _row_to_lines(values, widths, wrapper, sep='|', justify=JUSTIFY_LEFT):
|
||||
cells = []
|
||||
for value, width in zip(values, widths):
|
||||
wrapper.width = width
|
||||
text = to_unicode(value)
|
||||
text = to_str(value)
|
||||
cell = wrapper.wrap(text)
|
||||
cells.append(cell)
|
||||
lines = izip_longest(*cells, fillvalue='')
|
||||
lines = zip_longest(*cells, fillvalue='')
|
||||
lines = (
|
||||
(just(cell_line, widths[i]) for i, cell_line in enumerate(line))
|
||||
for line in lines
|
||||
@@ -206,11 +197,11 @@ def _use_simple_table(head0, col0, width0):
|
||||
|
||||
"""
|
||||
if head0 is not None:
|
||||
head0 = to_unicode(head0)
|
||||
head0 = to_str(head0)
|
||||
if len(head0) > width0:
|
||||
return False
|
||||
for cell in col0:
|
||||
cell = to_unicode(cell)
|
||||
cell = to_str(cell)
|
||||
if len(cell) > width0:
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Tablib - TSV (Tab Separated Values) Support.
|
||||
"""
|
||||
|
||||
from tablib.compat import unicode
|
||||
from tablib.formats._csv import (
|
||||
export_set as export_set_wrapper,
|
||||
import_set as import_set_wrapper,
|
||||
@@ -13,7 +10,7 @@ from tablib.formats._csv import (
|
||||
title = 'tsv'
|
||||
extensions = ('tsv',)
|
||||
|
||||
DELIMITER = unicode('\t')
|
||||
DELIMITER = '\t'
|
||||
|
||||
def export_set(dataset):
|
||||
"""Returns TSV representation of Dataset."""
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Tablib - XLS Support.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from io import BytesIO
|
||||
|
||||
from tablib.compat import xrange
|
||||
import tablib
|
||||
import xlrd
|
||||
import xlwt
|
||||
@@ -77,7 +74,7 @@ def import_set(dset, in_stream, headers=True):
|
||||
|
||||
dset.title = sheet.name
|
||||
|
||||
for i in xrange(sheet.nrows):
|
||||
for i in range(sheet.nrows):
|
||||
if (i == 0) and (headers):
|
||||
dset.headers = sheet.row_values(0)
|
||||
else:
|
||||
@@ -94,7 +91,7 @@ def import_book(dbook, in_stream, headers=True):
|
||||
data = tablib.Dataset()
|
||||
data.title = sheet.name
|
||||
|
||||
for i in xrange(sheet.nrows):
|
||||
for i in range(sheet.nrows):
|
||||
if (i == 0) and (headers):
|
||||
data.headers = sheet.row_values(0)
|
||||
else:
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Tablib - XLSX Support.
|
||||
"""
|
||||
|
||||
@@ -13,8 +11,6 @@ Workbook = openpyxl.workbook.Workbook
|
||||
ExcelWriter = openpyxl.writer.excel.ExcelWriter
|
||||
get_column_letter = openpyxl.utils.get_column_letter
|
||||
|
||||
from tablib.compat import unicode
|
||||
|
||||
|
||||
title = 'xlsx'
|
||||
extensions = ('xlsx',)
|
||||
@@ -132,7 +128,7 @@ def dset_sheet(dataset, ws, freeze_panes=True):
|
||||
# wrap the rest
|
||||
else:
|
||||
try:
|
||||
str_col_value = unicode(col)
|
||||
str_col_value = str(col)
|
||||
except TypeError:
|
||||
str_col_value = ''
|
||||
if '\n' in str_col_value:
|
||||
@@ -141,4 +137,4 @@ def dset_sheet(dataset, ws, freeze_panes=True):
|
||||
try:
|
||||
cell.value = col
|
||||
except (ValueError, TypeError):
|
||||
cell.value = unicode(col)
|
||||
cell.value = str(col)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" Tablib - YAML Support.
|
||||
"""
|
||||
|
||||
|
||||
@@ -64,10 +64,10 @@ __all__ = ["Dbf"]
|
||||
|
||||
from . import header
|
||||
from . import record
|
||||
from utils import INVALID_VALUE
|
||||
from .utils import INVALID_VALUE
|
||||
|
||||
|
||||
class Dbf(object):
|
||||
class Dbf:
|
||||
"""DBF accessor.
|
||||
|
||||
FIXME:
|
||||
@@ -114,16 +114,16 @@ class Dbf(object):
|
||||
``INVALID_VALUE`` instead of raising conversion error.
|
||||
|
||||
"""
|
||||
if isinstance(f, basestring):
|
||||
if isinstance(f, str):
|
||||
# a filename
|
||||
self.name = f
|
||||
if new:
|
||||
# new table (table file must be
|
||||
# created or opened and truncated)
|
||||
self.stream = file(f, "w+b")
|
||||
self.stream = open(f, "w+b")
|
||||
else:
|
||||
# table file must exist
|
||||
self.stream = file(f, ("r+b", "rb")[bool(readOnly)])
|
||||
self.stream = open(f, ("r+b", "rb")[bool(readOnly)])
|
||||
else:
|
||||
# a stream
|
||||
self.name = getattr(f, "name", "")
|
||||
@@ -177,7 +177,7 @@ class Dbf(object):
|
||||
Return value is numeric object maning valid index.
|
||||
|
||||
"""
|
||||
if not isinstance(index, (int, long)):
|
||||
if not isinstance(index, int):
|
||||
raise TypeError("Index must be a numeric object")
|
||||
if index < 0:
|
||||
# index from the right side
|
||||
@@ -204,7 +204,8 @@ class Dbf(object):
|
||||
def indexOfFieldName(self, name):
|
||||
"""Index of field named ``name``."""
|
||||
# FIXME: move this to header class
|
||||
return self.header.fields.index(name)
|
||||
names = [f.name for f in self.header.fields]
|
||||
return names.index(name.upper())
|
||||
|
||||
def newRecord(self):
|
||||
"""Return new record, which belong to this table."""
|
||||
@@ -294,4 +295,4 @@ if __name__ == '__main__':
|
||||
demo_create(_name)
|
||||
demo_read(_name)
|
||||
|
||||
# vim: set et sw=4 sts=4 :
|
||||
# vim: set et sw=4 sts=4 :
|
||||
|
||||
@@ -24,13 +24,13 @@ __date__ = "$Date: 2006/07/04 08:18:18 $"[7:-2]
|
||||
|
||||
__all__ = ["dbf_new"]
|
||||
|
||||
from dbf import *
|
||||
from fields import *
|
||||
from header import *
|
||||
from record import *
|
||||
from .dbf import *
|
||||
from .fields import *
|
||||
from .header import *
|
||||
from .record import *
|
||||
|
||||
|
||||
class _FieldDefinition(object):
|
||||
class _FieldDefinition:
|
||||
"""Field definition.
|
||||
|
||||
This is a simple structure, which contains ``name``, ``type``,
|
||||
@@ -87,7 +87,7 @@ class _FieldDefinition(object):
|
||||
dbfh.addField(_dbff)
|
||||
|
||||
|
||||
class dbf_new(object):
|
||||
class dbf_new:
|
||||
"""New .DBF creation helper.
|
||||
|
||||
Example Usage:
|
||||
@@ -140,17 +140,11 @@ class dbf_new(object):
|
||||
_dbfh.setCurrentDate()
|
||||
for _fldDef in self.fields:
|
||||
_fldDef.appendToHeader(_dbfh)
|
||||
_dbfStream = file(filename, "wb")
|
||||
|
||||
_dbfStream = open(filename, "wb")
|
||||
_dbfh.write(_dbfStream)
|
||||
_dbfStream.close()
|
||||
|
||||
def write_stream(self, stream):
|
||||
_dbfh = DbfHeader()
|
||||
_dbfh.setCurrentDate()
|
||||
for _fldDef in self.fields:
|
||||
_fldDef.appendToHeader(_dbfh)
|
||||
_dbfh.write(stream)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# create a new DBF-File
|
||||
@@ -186,4 +180,4 @@ if __name__ == '__main__':
|
||||
print()
|
||||
dbft.close()
|
||||
|
||||
# vim: set et sts=4 sw=4 :
|
||||
# vim: set et sts=4 sw=4 :
|
||||
|
||||
@@ -38,7 +38,7 @@ from . import utils
|
||||
|
||||
## abstract definitions
|
||||
|
||||
class DbfFieldDef(object):
|
||||
class DbfFieldDef:
|
||||
"""Abstract field definition.
|
||||
|
||||
Child classes must override ``type`` class attribute to provide datatype
|
||||
@@ -56,7 +56,7 @@ class DbfFieldDef(object):
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ("name", "length", "decimalCount",
|
||||
__slots__ = ("name", "decimalCount",
|
||||
"start", "end", "ignoreErrors")
|
||||
|
||||
# length of the field, None in case of variable-length field,
|
||||
@@ -123,9 +123,9 @@ class DbfFieldDef(object):
|
||||
|
||||
"""
|
||||
assert len(string) == 32
|
||||
_length = ord(string[16])
|
||||
return cls(utils.unzfill(string)[:11], _length, ord(string[17]),
|
||||
start, start + _length, ignoreErrors=ignoreErrors)
|
||||
_length = string[16]
|
||||
return cls(utils.unzfill(string)[:11].decode('utf-8'), _length,
|
||||
string[17], start, start + _length, ignoreErrors=ignoreErrors)
|
||||
fromString = classmethod(fromString)
|
||||
|
||||
def toString(self):
|
||||
@@ -200,7 +200,7 @@ class DbfCharacterFieldDef(DbfFieldDef):
|
||||
"""Definition of the character field."""
|
||||
|
||||
typeCode = "C"
|
||||
defaultValue = ""
|
||||
defaultValue = b''
|
||||
|
||||
def decodeValue(self, value):
|
||||
"""Return string object.
|
||||
@@ -208,7 +208,7 @@ class DbfCharacterFieldDef(DbfFieldDef):
|
||||
Return value is a ``value`` argument with stripped right spaces.
|
||||
|
||||
"""
|
||||
return value.rstrip(" ")
|
||||
return value.rstrip(b' ').decode('utf-8')
|
||||
|
||||
def encodeValue(self, value):
|
||||
"""Return raw data string encoded from a ``value``."""
|
||||
@@ -235,8 +235,8 @@ class DbfNumericFieldDef(DbfFieldDef):
|
||||
Return value is a int (long) or float instance.
|
||||
|
||||
"""
|
||||
value = value.strip(" \0")
|
||||
if "." in value:
|
||||
value = value.strip(b' \0')
|
||||
if b'.' in value:
|
||||
# a float (has decimal separator)
|
||||
return float(value)
|
||||
elif value:
|
||||
@@ -452,11 +452,11 @@ def lookupFor(typeCode):
|
||||
"""
|
||||
# XXX: use typeCode.upper()? in case of any decign don't
|
||||
# forget to look to the same comment in ``registerField``
|
||||
return _fieldsRegistry[typeCode]
|
||||
return _fieldsRegistry[chr(typeCode)]
|
||||
|
||||
## register generic types
|
||||
|
||||
for (_name, _val) in globals().items():
|
||||
for (_name, _val) in list(globals().items()):
|
||||
if isinstance(_val, type) and issubclass(_val, DbfFieldDef) \
|
||||
and (_name != "DbfFieldDef"):
|
||||
__all__.append(_name)
|
||||
|
||||
@@ -19,20 +19,17 @@ __date__ = "$Date: 2010/09/16 05:06:39 $"[7:-2]
|
||||
|
||||
__all__ = ["DbfHeader"]
|
||||
|
||||
try:
|
||||
import cStringIO
|
||||
except ImportError:
|
||||
# when we're in python3, we cStringIO has been replaced by io.StringIO
|
||||
import io as cStringIO
|
||||
import io
|
||||
import datetime
|
||||
import struct
|
||||
import time
|
||||
import sys
|
||||
|
||||
from . import fields
|
||||
from . import utils
|
||||
from .utils import getDate
|
||||
|
||||
|
||||
class DbfHeader(object):
|
||||
class DbfHeader:
|
||||
"""Dbf header definition.
|
||||
|
||||
For more information about dbf header format visit
|
||||
@@ -90,7 +87,7 @@ class DbfHeader(object):
|
||||
self.fields = []
|
||||
else:
|
||||
self.fields = list(fields)
|
||||
self.lastUpdate = utils.getDate(lastUpdate)
|
||||
self.lastUpdate = getDate(lastUpdate)
|
||||
self.recordLength = recordLength
|
||||
self.headerLength = headerLength
|
||||
self.recordCount = recordCount
|
||||
@@ -102,17 +99,20 @@ class DbfHeader(object):
|
||||
# @classmethod
|
||||
def fromString(cls, string):
|
||||
"""Return header instance from the string object."""
|
||||
return cls.fromStream(cStringIO.StringIO(str(string)))
|
||||
return cls.fromStream(io.StringIO(str(string)))
|
||||
fromString = classmethod(fromString)
|
||||
|
||||
# @classmethod
|
||||
def fromStream(cls, stream):
|
||||
"""Return header object from the stream."""
|
||||
stream.seek(0)
|
||||
_data = stream.read(32)
|
||||
first_32 = stream.read(32)
|
||||
if type(first_32) != bytes:
|
||||
_data = bytes(first_32, sys.getfilesystemencoding())
|
||||
_data = first_32
|
||||
(_cnt, _hdrLen, _recLen) = struct.unpack("<I2H", _data[4:12])
|
||||
#reserved = _data[12:32]
|
||||
_year = ord(_data[1])
|
||||
_year = _data[1]
|
||||
if _year < 80:
|
||||
# dBase II started at 1980. It is quite unlikely
|
||||
# that actual last update date is before that year.
|
||||
@@ -120,17 +120,13 @@ class DbfHeader(object):
|
||||
else:
|
||||
_year += 1900
|
||||
## create header object
|
||||
_obj = cls(None, _hdrLen, _recLen, _cnt, ord(_data[0]),
|
||||
(_year, ord(_data[2]), ord(_data[3])))
|
||||
_obj = cls(None, _hdrLen, _recLen, _cnt, _data[0],
|
||||
(_year, _data[2], _data[3]))
|
||||
## append field definitions
|
||||
# position 0 is for the deletion flag
|
||||
_pos = 1
|
||||
_data = stream.read(1)
|
||||
|
||||
# The field definitions are ended either by \x0D OR a newline
|
||||
# character, so we need to handle both when reading from a stream.
|
||||
# When writing, dbfpy appears to write newlines instead of \x0D.
|
||||
while _data[0] not in ["\x0D", "\n"]:
|
||||
while _data != b'\r':
|
||||
_data += stream.read(31)
|
||||
_fld = fields.lookupFor(_data[11]).fromString(_data, _pos)
|
||||
_obj._addField(_fld)
|
||||
@@ -240,8 +236,9 @@ Version (signature): 0x%02x
|
||||
"""Encode and write header to the stream."""
|
||||
stream.seek(0)
|
||||
stream.write(self.toString())
|
||||
stream.write("".join([_fld.toString() for _fld in self.fields]))
|
||||
stream.write(chr(0x0D)) # cr at end of all hdr data
|
||||
fields = [_fld.toString() for _fld in self.fields]
|
||||
stream.write(''.join(fields).encode(sys.getfilesystemencoding()))
|
||||
stream.write(b'\x0D') # cr at end of all header data
|
||||
self.changed = False
|
||||
|
||||
def toString(self):
|
||||
@@ -253,7 +250,8 @@ Version (signature): 0x%02x
|
||||
self.day,
|
||||
self.recordCount,
|
||||
self.headerLength,
|
||||
self.recordLength) + "\0" * 20
|
||||
self.recordLength) + (b'\x00' * 20)
|
||||
#TODO: figure out if bytes(utf-8) is correct here.
|
||||
|
||||
def setCurrentDate(self):
|
||||
"""Update ``self.lastUpdate`` field with current date value."""
|
||||
@@ -261,7 +259,7 @@ Version (signature): 0x%02x
|
||||
|
||||
def __getitem__(self, item):
|
||||
"""Return a field definition by numeric index or name string"""
|
||||
if isinstance(item, basestring):
|
||||
if isinstance(item, str):
|
||||
_name = item.upper()
|
||||
for _field in self.fields:
|
||||
if _field.name == _name:
|
||||
|
||||
@@ -16,11 +16,11 @@ __date__ = "$Date: 2007/02/11 09:05:49 $"[7:-2]
|
||||
|
||||
__all__ = ["DbfRecord"]
|
||||
|
||||
from itertools import izip
|
||||
import sys
|
||||
|
||||
import utils
|
||||
from . import utils
|
||||
|
||||
class DbfRecord(object):
|
||||
class DbfRecord:
|
||||
"""DBF record.
|
||||
|
||||
Instances of this class shouldn't be created manually,
|
||||
@@ -170,13 +170,14 @@ class DbfRecord(object):
|
||||
"""
|
||||
self._validateIndex(False)
|
||||
self.dbf.stream.seek(self.position)
|
||||
self.dbf.stream.write(self.toString())
|
||||
self.dbf.stream.write(bytes(self.toString(),
|
||||
sys.getfilesystemencoding()))
|
||||
# FIXME: may be move this write somewhere else?
|
||||
# why we should check this condition for each record?
|
||||
if self.index == len(self.dbf):
|
||||
# this is the last record,
|
||||
# we should write SUB (ASCII 26)
|
||||
self.dbf.stream.write("\x1A")
|
||||
self.dbf.stream.write(b"\x1A")
|
||||
|
||||
## utility methods
|
||||
|
||||
@@ -218,9 +219,12 @@ class DbfRecord(object):
|
||||
|
||||
def toString(self):
|
||||
"""Return string packed record values."""
|
||||
# for (_def, _dat) in zip(self.dbf.header.fields, self.fieldData):
|
||||
#
|
||||
|
||||
return "".join([" *"[self.deleted]] + [
|
||||
_def.encodeValue(_dat)
|
||||
for (_def, _dat) in izip(self.dbf.header.fields, self.fieldData)
|
||||
_def.encodeValue(_dat)
|
||||
for (_def, _dat) in zip(self.dbf.header.fields, self.fieldData)
|
||||
])
|
||||
|
||||
def asList(self):
|
||||
@@ -241,11 +245,11 @@ class DbfRecord(object):
|
||||
real values stored in this object.
|
||||
|
||||
"""
|
||||
return dict([_i for _i in izip(self.dbf.fieldNames, self.fieldData)])
|
||||
return dict([_i for _i in zip(self.dbf.fieldNames, self.fieldData)])
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Return value by field name or field index."""
|
||||
if isinstance(key, (long, int)):
|
||||
if isinstance(key, int):
|
||||
# integer index of the field
|
||||
return self.fieldData[key]
|
||||
# assuming string field name
|
||||
@@ -253,7 +257,7 @@ class DbfRecord(object):
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"""Set field value by integer index of the field or string name."""
|
||||
if isinstance(key, (int, long)):
|
||||
if isinstance(key, int):
|
||||
# integer index of the field
|
||||
return self.fieldData[key]
|
||||
# assuming string field name
|
||||
|
||||
@@ -25,7 +25,7 @@ def unzfill(str):
|
||||
|
||||
"""
|
||||
try:
|
||||
return str[:str.index('\0')]
|
||||
return str[:str.index(b'\0')]
|
||||
except ValueError:
|
||||
return str
|
||||
|
||||
@@ -60,10 +60,10 @@ def getDate(date=None):
|
||||
return date
|
||||
if isinstance(date, datetime.datetime):
|
||||
return date.date()
|
||||
if isinstance(date, (int, long, float)):
|
||||
if isinstance(date, (int, float)):
|
||||
# date is a timestamp
|
||||
return datetime.date.fromtimestamp(date)
|
||||
if isinstance(date, basestring):
|
||||
if isinstance(date, str):
|
||||
date = date.replace(" ", "0")
|
||||
if len(date) == 6:
|
||||
# yymmdd
|
||||
@@ -107,10 +107,10 @@ def getDateTime(value=None):
|
||||
return value
|
||||
if isinstance(value, datetime.date):
|
||||
return datetime.datetime.fromordinal(value.toordinal())
|
||||
if isinstance(value, (int, long, float)):
|
||||
if isinstance(value, (int, float)):
|
||||
# value is a timestamp
|
||||
return datetime.datetime.fromtimestamp(value)
|
||||
if isinstance(value, basestring):
|
||||
if isinstance(value, str):
|
||||
raise NotImplementedError("Strings aren't currently implemented")
|
||||
if hasattr(value, "__getitem__"):
|
||||
# a sequence (assuming date/time tuple)
|
||||
@@ -125,7 +125,7 @@ class classproperty(property):
|
||||
return self.fget(cls)
|
||||
|
||||
|
||||
class _InvalidValue(object):
|
||||
class _InvalidValue:
|
||||
|
||||
"""Value returned from DBF records when field validation fails
|
||||
|
||||
@@ -145,7 +145,7 @@ class _InvalidValue(object):
|
||||
def __ne__(self, other):
|
||||
return not (other is self)
|
||||
|
||||
def __nonzero__(self):
|
||||
def __bool__(self):
|
||||
return False
|
||||
|
||||
def __int__(self):
|
||||
@@ -159,7 +159,7 @@ class _InvalidValue(object):
|
||||
return ""
|
||||
|
||||
def __unicode__(self):
|
||||
return u""
|
||||
return ""
|
||||
|
||||
def __repr__(self):
|
||||
return "<INVALID>"
|
||||
|
||||
@@ -1,297 +0,0 @@
|
||||
#! /usr/bin/env python
|
||||
"""DBF accessing helpers.
|
||||
|
||||
FIXME: more documentation needed
|
||||
|
||||
Examples:
|
||||
|
||||
Create new table, setup structure, add records:
|
||||
|
||||
dbf = Dbf(filename, new=True)
|
||||
dbf.addField(
|
||||
("NAME", "C", 15),
|
||||
("SURNAME", "C", 25),
|
||||
("INITIALS", "C", 10),
|
||||
("BIRTHDATE", "D"),
|
||||
)
|
||||
for (n, s, i, b) in (
|
||||
("John", "Miller", "YC", (1980, 10, 11)),
|
||||
("Andy", "Larkin", "", (1980, 4, 11)),
|
||||
):
|
||||
rec = dbf.newRecord()
|
||||
rec["NAME"] = n
|
||||
rec["SURNAME"] = s
|
||||
rec["INITIALS"] = i
|
||||
rec["BIRTHDATE"] = b
|
||||
rec.store()
|
||||
dbf.close()
|
||||
|
||||
Open existed dbf, read some data:
|
||||
|
||||
dbf = Dbf(filename, True)
|
||||
for rec in dbf:
|
||||
for fldName in dbf.fieldNames:
|
||||
print('%s:\t %s (%s)' % (fldName, rec[fldName],
|
||||
type(rec[fldName])))
|
||||
dbf.close()
|
||||
|
||||
"""
|
||||
"""History (most recent first):
|
||||
11-feb-2007 [als] export INVALID_VALUE;
|
||||
Dbf: added .ignoreErrors, .INVALID_VALUE
|
||||
04-jul-2006 [als] added export declaration
|
||||
20-dec-2005 [yc] removed fromStream and newDbf methods:
|
||||
use argument of __init__ call must be used instead;
|
||||
added class fields pointing to the header and
|
||||
record classes.
|
||||
17-dec-2005 [yc] split to several modules; reimplemented
|
||||
13-dec-2005 [yc] adapted to the changes of the `strutil` module.
|
||||
13-sep-2002 [als] support FoxPro Timestamp datatype
|
||||
15-nov-1999 [jjk] documentation updates, add demo
|
||||
24-aug-1998 [jjk] add some encodeValue methods (not tested), other tweaks
|
||||
08-jun-1998 [jjk] fix problems, add more features
|
||||
20-feb-1998 [jjk] fix problems, add more features
|
||||
19-feb-1998 [jjk] add create/write capabilities
|
||||
18-feb-1998 [jjk] from dbfload.py
|
||||
"""
|
||||
|
||||
__version__ = "$Revision: 1.7 $"[11:-2]
|
||||
__date__ = "$Date: 2007/02/11 09:23:13 $"[7:-2]
|
||||
__author__ = "Jeff Kunce <kuncej@mail.conservation.state.mo.us>"
|
||||
|
||||
__all__ = ["Dbf"]
|
||||
|
||||
from . import header
|
||||
from . import record
|
||||
from .utils import INVALID_VALUE
|
||||
|
||||
|
||||
class Dbf(object):
|
||||
"""DBF accessor.
|
||||
|
||||
FIXME:
|
||||
docs and examples needed (dont' forget to tell
|
||||
about problems adding new fields on the fly)
|
||||
|
||||
Implementation notes:
|
||||
``_new`` field is used to indicate whether this is
|
||||
a new data table. `addField` could be used only for
|
||||
the new tables! If at least one record was appended
|
||||
to the table it's structure couldn't be changed.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ("name", "header", "stream",
|
||||
"_changed", "_new", "_ignore_errors")
|
||||
|
||||
HeaderClass = header.DbfHeader
|
||||
RecordClass = record.DbfRecord
|
||||
INVALID_VALUE = INVALID_VALUE
|
||||
|
||||
# initialization and creation helpers
|
||||
|
||||
def __init__(self, f, readOnly=False, new=False, ignoreErrors=False):
|
||||
"""Initialize instance.
|
||||
|
||||
Arguments:
|
||||
f:
|
||||
Filename or file-like object.
|
||||
new:
|
||||
True if new data table must be created. Assume
|
||||
data table exists if this argument is False.
|
||||
readOnly:
|
||||
if ``f`` argument is a string file will
|
||||
be opend in read-only mode; in other cases
|
||||
this argument is ignored. This argument is ignored
|
||||
even if ``new`` argument is True.
|
||||
headerObj:
|
||||
`header.DbfHeader` instance or None. If this argument
|
||||
is None, new empty header will be used with the
|
||||
all fields set by default.
|
||||
ignoreErrors:
|
||||
if set, failing field value conversion will return
|
||||
``INVALID_VALUE`` instead of raising conversion error.
|
||||
|
||||
"""
|
||||
if isinstance(f, str):
|
||||
# a filename
|
||||
self.name = f
|
||||
if new:
|
||||
# new table (table file must be
|
||||
# created or opened and truncated)
|
||||
self.stream = open(f, "w+b")
|
||||
else:
|
||||
# table file must exist
|
||||
self.stream = open(f, ("r+b", "rb")[bool(readOnly)])
|
||||
else:
|
||||
# a stream
|
||||
self.name = getattr(f, "name", "")
|
||||
self.stream = f
|
||||
if new:
|
||||
# if this is a new table, header will be empty
|
||||
self.header = self.HeaderClass()
|
||||
else:
|
||||
# or instantiated using stream
|
||||
self.header = self.HeaderClass.fromStream(self.stream)
|
||||
self.ignoreErrors = ignoreErrors
|
||||
self._new = bool(new)
|
||||
self._changed = False
|
||||
|
||||
# properties
|
||||
|
||||
closed = property(lambda self: self.stream.closed)
|
||||
recordCount = property(lambda self: self.header.recordCount)
|
||||
fieldNames = property(
|
||||
lambda self: [_fld.name for _fld in self.header.fields])
|
||||
fieldDefs = property(lambda self: self.header.fields)
|
||||
changed = property(lambda self: self._changed or self.header.changed)
|
||||
|
||||
def ignoreErrors(self, value):
|
||||
"""Update `ignoreErrors` flag on the header object and self"""
|
||||
self.header.ignoreErrors = self._ignore_errors = bool(value)
|
||||
|
||||
ignoreErrors = property(
|
||||
lambda self: self._ignore_errors,
|
||||
ignoreErrors,
|
||||
doc="""Error processing mode for DBF field value conversion
|
||||
|
||||
if set, failing field value conversion will return
|
||||
``INVALID_VALUE`` instead of raising conversion error.
|
||||
|
||||
""")
|
||||
|
||||
# protected methods
|
||||
|
||||
def _fixIndex(self, index):
|
||||
"""Return fixed index.
|
||||
|
||||
This method fails if index isn't a numeric object
|
||||
(long or int). Or index isn't in a valid range
|
||||
(less or equal to the number of records in the db).
|
||||
|
||||
If ``index`` is a negative number, it will be
|
||||
treated as a negative indexes for list objects.
|
||||
|
||||
Return:
|
||||
Return value is numeric object maning valid index.
|
||||
|
||||
"""
|
||||
if not isinstance(index, int):
|
||||
raise TypeError("Index must be a numeric object")
|
||||
if index < 0:
|
||||
# index from the right side
|
||||
# fix it to the left-side index
|
||||
index += len(self) + 1
|
||||
if index >= len(self):
|
||||
raise IndexError("Record index out of range")
|
||||
return index
|
||||
|
||||
# interface methods
|
||||
|
||||
def close(self):
|
||||
self.flush()
|
||||
self.stream.close()
|
||||
|
||||
def flush(self):
|
||||
"""Flush data to the associated stream."""
|
||||
if self.changed:
|
||||
self.header.setCurrentDate()
|
||||
self.header.write(self.stream)
|
||||
self.stream.flush()
|
||||
self._changed = False
|
||||
|
||||
def indexOfFieldName(self, name):
|
||||
"""Index of field named ``name``."""
|
||||
# FIXME: move this to header class
|
||||
names = [f.name for f in self.header.fields]
|
||||
return names.index(name.upper())
|
||||
|
||||
def newRecord(self):
|
||||
"""Return new record, which belong to this table."""
|
||||
return self.RecordClass(self)
|
||||
|
||||
def append(self, record):
|
||||
"""Append ``record`` to the database."""
|
||||
record.index = self.header.recordCount
|
||||
record._write()
|
||||
self.header.recordCount += 1
|
||||
self._changed = True
|
||||
self._new = False
|
||||
|
||||
def addField(self, *defs):
|
||||
"""Add field definitions.
|
||||
|
||||
For more information see `header.DbfHeader.addField`.
|
||||
|
||||
"""
|
||||
if self._new:
|
||||
self.header.addField(*defs)
|
||||
else:
|
||||
raise TypeError("At least one record was added, "
|
||||
"structure can't be changed")
|
||||
|
||||
# 'magic' methods (representation and sequence interface)
|
||||
|
||||
def __repr__(self):
|
||||
return "Dbf stream '%s'\n" % self.stream + repr(self.header)
|
||||
|
||||
def __len__(self):
|
||||
"""Return number of records."""
|
||||
return self.recordCount
|
||||
|
||||
def __getitem__(self, index):
|
||||
"""Return `DbfRecord` instance."""
|
||||
return self.RecordClass.fromStream(self, self._fixIndex(index))
|
||||
|
||||
def __setitem__(self, index, record):
|
||||
"""Write `DbfRecord` instance to the stream."""
|
||||
record.index = self._fixIndex(index)
|
||||
record._write()
|
||||
self._changed = True
|
||||
self._new = False
|
||||
|
||||
# def __del__(self):
|
||||
# """Flush stream upon deletion of the object."""
|
||||
# self.flush()
|
||||
|
||||
|
||||
def demo_read(filename):
|
||||
_dbf = Dbf(filename, True)
|
||||
for _rec in _dbf:
|
||||
print()
|
||||
print(repr(_rec))
|
||||
_dbf.close()
|
||||
|
||||
|
||||
def demo_create(filename):
|
||||
_dbf = Dbf(filename, new=True)
|
||||
_dbf.addField(
|
||||
("NAME", "C", 15),
|
||||
("SURNAME", "C", 25),
|
||||
("INITIALS", "C", 10),
|
||||
("BIRTHDATE", "D"),
|
||||
)
|
||||
for (_n, _s, _i, _b) in (
|
||||
("John", "Miller", "YC", (1981, 1, 2)),
|
||||
("Andy", "Larkin", "AL", (1982, 3, 4)),
|
||||
("Bill", "Clinth", "", (1983, 5, 6)),
|
||||
("Bobb", "McNail", "", (1984, 7, 8)),
|
||||
):
|
||||
_rec = _dbf.newRecord()
|
||||
_rec["NAME"] = _n
|
||||
_rec["SURNAME"] = _s
|
||||
_rec["INITIALS"] = _i
|
||||
_rec["BIRTHDATE"] = _b
|
||||
_rec.store()
|
||||
print(repr(_dbf))
|
||||
_dbf.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
_name = len(sys.argv) > 1 and sys.argv[1] or "county.dbf"
|
||||
demo_create(_name)
|
||||
demo_read(_name)
|
||||
|
||||
# vim: set et sw=4 sts=4 :
|
||||
@@ -1,183 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
""".DBF creation helpers.
|
||||
|
||||
Note: this is a legacy interface. New code should use Dbf class
|
||||
for table creation (see examples in dbf.py)
|
||||
|
||||
TODO:
|
||||
- handle Memo fields.
|
||||
- check length of the fields according to the
|
||||
`http://www.clicketyclick.dk/databases/xbase/format/data_types.html`
|
||||
|
||||
"""
|
||||
"""History (most recent first)
|
||||
04-jul-2006 [als] added export declaration;
|
||||
updated for dbfpy 2.0
|
||||
15-dec-2005 [yc] define dbf_new.__slots__
|
||||
14-dec-2005 [yc] added vim modeline; retab'd; added doc-strings;
|
||||
dbf_new now is a new class (inherited from object)
|
||||
??-jun-2000 [--] added by Hans Fiby
|
||||
"""
|
||||
|
||||
__version__ = "$Revision: 1.4 $"[11:-2]
|
||||
__date__ = "$Date: 2006/07/04 08:18:18 $"[7:-2]
|
||||
|
||||
__all__ = ["dbf_new"]
|
||||
|
||||
from .dbf import *
|
||||
from .fields import *
|
||||
from .header import *
|
||||
from .record import *
|
||||
|
||||
|
||||
class _FieldDefinition(object):
|
||||
"""Field definition.
|
||||
|
||||
This is a simple structure, which contains ``name``, ``type``,
|
||||
``len``, ``dec`` and ``cls`` fields.
|
||||
|
||||
Objects also implement get/setitem magic functions, so fields
|
||||
could be accessed via sequence interface, where 'name' has
|
||||
index 0, 'type' index 1, 'len' index 2, 'dec' index 3 and
|
||||
'cls' could be located at index 4.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = "name", "type", "len", "dec", "cls"
|
||||
|
||||
# WARNING: be attentive - dictionaries are mutable!
|
||||
FLD_TYPES = {
|
||||
# type: (cls, len)
|
||||
"C": (DbfCharacterFieldDef, None),
|
||||
"N": (DbfNumericFieldDef, None),
|
||||
"L": (DbfLogicalFieldDef, 1),
|
||||
# FIXME: support memos
|
||||
# "M": (DbfMemoFieldDef),
|
||||
"D": (DbfDateFieldDef, 8),
|
||||
# FIXME: I'm not sure length should be 14 characters!
|
||||
# but temporary I use it, cuz date is 8 characters
|
||||
# and time 6 (hhmmss)
|
||||
"T": (DbfDateTimeFieldDef, 14),
|
||||
}
|
||||
|
||||
def __init__(self, name, type, len=None, dec=0):
|
||||
_cls, _len = self.FLD_TYPES[type]
|
||||
if _len is None:
|
||||
if len is None:
|
||||
raise ValueError("Field length must be defined")
|
||||
_len = len
|
||||
self.name = name
|
||||
self.type = type
|
||||
self.len = _len
|
||||
self.dec = dec
|
||||
self.cls = _cls
|
||||
|
||||
def getDbfField(self):
|
||||
"Return `DbfFieldDef` instance from the current definition."
|
||||
return self.cls(self.name, self.len, self.dec)
|
||||
|
||||
def appendToHeader(self, dbfh):
|
||||
"""Create a `DbfFieldDef` instance and append it to the dbf header.
|
||||
|
||||
Arguments:
|
||||
dbfh: `DbfHeader` instance.
|
||||
|
||||
"""
|
||||
_dbff = self.getDbfField()
|
||||
dbfh.addField(_dbff)
|
||||
|
||||
|
||||
class dbf_new(object):
|
||||
"""New .DBF creation helper.
|
||||
|
||||
Example Usage:
|
||||
|
||||
dbfn = dbf_new()
|
||||
dbfn.add_field("name",'C',80)
|
||||
dbfn.add_field("price",'N',10,2)
|
||||
dbfn.add_field("date",'D',8)
|
||||
dbfn.write("tst.dbf")
|
||||
|
||||
Note:
|
||||
This module cannot handle Memo-fields,
|
||||
they are special.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ("fields",)
|
||||
|
||||
FieldDefinitionClass = _FieldDefinition
|
||||
|
||||
def __init__(self):
|
||||
self.fields = []
|
||||
|
||||
def add_field(self, name, typ, len, dec=0):
|
||||
"""Add field definition.
|
||||
|
||||
Arguments:
|
||||
name:
|
||||
field name (str object). field name must not
|
||||
contain ASCII NULs and it's length shouldn't
|
||||
exceed 10 characters.
|
||||
typ:
|
||||
type of the field. this must be a single character
|
||||
from the "CNLMDT" set meaning character, numeric,
|
||||
logical, memo, date and date/time respectively.
|
||||
len:
|
||||
length of the field. this argument is used only for
|
||||
the character and numeric fields. all other fields
|
||||
have fixed length.
|
||||
FIXME: use None as a default for this argument?
|
||||
dec:
|
||||
decimal precision. used only for the numric fields.
|
||||
|
||||
"""
|
||||
self.fields.append(self.FieldDefinitionClass(name, typ, len, dec))
|
||||
|
||||
def write(self, filename):
|
||||
"""Create empty .DBF file using current structure."""
|
||||
_dbfh = DbfHeader()
|
||||
_dbfh.setCurrentDate()
|
||||
for _fldDef in self.fields:
|
||||
_fldDef.appendToHeader(_dbfh)
|
||||
|
||||
_dbfStream = open(filename, "wb")
|
||||
_dbfh.write(_dbfStream)
|
||||
_dbfStream.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# create a new DBF-File
|
||||
dbfn = dbf_new()
|
||||
dbfn.add_field("name", 'C', 80)
|
||||
dbfn.add_field("price", 'N', 10, 2)
|
||||
dbfn.add_field("date", 'D', 8)
|
||||
dbfn.write("tst.dbf")
|
||||
# test new dbf
|
||||
print("*** created tst.dbf: ***")
|
||||
dbft = Dbf('tst.dbf', readOnly=0)
|
||||
print(repr(dbft))
|
||||
# add a record
|
||||
rec = DbfRecord(dbft)
|
||||
rec['name'] = 'something'
|
||||
rec['price'] = 10.5
|
||||
rec['date'] = (2000, 1, 12)
|
||||
rec.store()
|
||||
# add another record
|
||||
rec = DbfRecord(dbft)
|
||||
rec['name'] = 'foo and bar'
|
||||
rec['price'] = 12234
|
||||
rec['date'] = (1992, 7, 15)
|
||||
rec.store()
|
||||
|
||||
# show the records
|
||||
print("*** inserted 2 records into tst.dbf: ***")
|
||||
print(repr(dbft))
|
||||
for i1 in range(len(dbft)):
|
||||
rec = dbft[i1]
|
||||
for fldName in dbft.fieldNames:
|
||||
print('%s:\t %s' % (fldName, rec[fldName]))
|
||||
print()
|
||||
dbft.close()
|
||||
|
||||
# vim: set et sts=4 sw=4 :
|
||||
@@ -1,466 +0,0 @@
|
||||
"""DBF fields definitions.
|
||||
|
||||
TODO:
|
||||
- make memos work
|
||||
"""
|
||||
"""History (most recent first):
|
||||
26-may-2009 [als] DbfNumericFieldDef.decodeValue: strip zero bytes
|
||||
05-feb-2009 [als] DbfDateFieldDef.encodeValue: empty arg produces empty date
|
||||
16-sep-2008 [als] DbfNumericFieldDef decoding looks for decimal point
|
||||
in the value to select float or integer return type
|
||||
13-mar-2008 [als] check field name length in constructor
|
||||
11-feb-2007 [als] handle value conversion errors
|
||||
10-feb-2007 [als] DbfFieldDef: added .rawFromRecord()
|
||||
01-dec-2006 [als] Timestamp columns use None for empty values
|
||||
31-oct-2006 [als] support field types 'F' (float), 'I' (integer)
|
||||
and 'Y' (currency);
|
||||
automate export and registration of field classes
|
||||
04-jul-2006 [als] added export declaration
|
||||
10-mar-2006 [als] decode empty values for Date and Logical fields;
|
||||
show field name in errors
|
||||
10-mar-2006 [als] fix Numeric value decoding: according to spec,
|
||||
value always is string representation of the number;
|
||||
ensure that encoded Numeric value fits into the field
|
||||
20-dec-2005 [yc] use field names in upper case
|
||||
15-dec-2005 [yc] field definitions moved from `dbf`.
|
||||
"""
|
||||
|
||||
__version__ = "$Revision: 1.14 $"[11:-2]
|
||||
__date__ = "$Date: 2009/05/26 05:16:51 $"[7:-2]
|
||||
|
||||
__all__ = ["lookupFor",] # field classes added at the end of the module
|
||||
|
||||
import datetime
|
||||
import struct
|
||||
import sys
|
||||
|
||||
from . import utils
|
||||
|
||||
## abstract definitions
|
||||
|
||||
class DbfFieldDef(object):
|
||||
"""Abstract field definition.
|
||||
|
||||
Child classes must override ``type`` class attribute to provide datatype
|
||||
information of the field definition. For more info about types visit
|
||||
`http://www.clicketyclick.dk/databases/xbase/format/data_types.html`
|
||||
|
||||
Also child classes must override ``defaultValue`` field to provide
|
||||
default value for the field value.
|
||||
|
||||
If child class has fixed length ``length`` class attribute must be
|
||||
overridden and set to the valid value. None value means, that field
|
||||
isn't of fixed length.
|
||||
|
||||
Note: ``name`` field must not be changed after instantiation.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ("name", "decimalCount",
|
||||
"start", "end", "ignoreErrors")
|
||||
|
||||
# length of the field, None in case of variable-length field,
|
||||
# or a number if this field is a fixed-length field
|
||||
length = None
|
||||
|
||||
# field type. for more information about fields types visit
|
||||
# `http://www.clicketyclick.dk/databases/xbase/format/data_types.html`
|
||||
# must be overridden in child classes
|
||||
typeCode = None
|
||||
|
||||
# default value for the field. this field must be
|
||||
# overridden in child classes
|
||||
defaultValue = None
|
||||
|
||||
def __init__(self, name, length=None, decimalCount=None,
|
||||
start=None, stop=None, ignoreErrors=False,
|
||||
):
|
||||
"""Initialize instance."""
|
||||
assert self.typeCode is not None, "Type code must be overridden"
|
||||
assert self.defaultValue is not None, "Default value must be overridden"
|
||||
## fix arguments
|
||||
if len(name) >10:
|
||||
raise ValueError("Field name \"%s\" is too long" % name)
|
||||
name = str(name).upper()
|
||||
if self.__class__.length is None:
|
||||
if length is None:
|
||||
raise ValueError("[%s] Length isn't specified" % name)
|
||||
length = int(length)
|
||||
if length <= 0:
|
||||
raise ValueError("[%s] Length must be a positive integer"
|
||||
% name)
|
||||
else:
|
||||
length = self.length
|
||||
if decimalCount is None:
|
||||
decimalCount = 0
|
||||
## set fields
|
||||
self.name = name
|
||||
# FIXME: validate length according to the specification at
|
||||
# http://www.clicketyclick.dk/databases/xbase/format/data_types.html
|
||||
self.length = length
|
||||
self.decimalCount = decimalCount
|
||||
self.ignoreErrors = ignoreErrors
|
||||
self.start = start
|
||||
self.end = stop
|
||||
|
||||
def __cmp__(self, other):
|
||||
return cmp(self.name, str(other).upper())
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.name)
|
||||
|
||||
def fromString(cls, string, start, ignoreErrors=False):
|
||||
"""Decode dbf field definition from the string data.
|
||||
|
||||
Arguments:
|
||||
string:
|
||||
a string, dbf definition is decoded from. length of
|
||||
the string must be 32 bytes.
|
||||
start:
|
||||
position in the database file.
|
||||
ignoreErrors:
|
||||
initial error processing mode for the new field (boolean)
|
||||
|
||||
"""
|
||||
assert len(string) == 32
|
||||
_length = string[16]
|
||||
return cls(utils.unzfill(string)[:11].decode('utf-8'), _length,
|
||||
string[17], start, start + _length, ignoreErrors=ignoreErrors)
|
||||
fromString = classmethod(fromString)
|
||||
|
||||
def toString(self):
|
||||
"""Return encoded field definition.
|
||||
|
||||
Return:
|
||||
Return value is a string object containing encoded
|
||||
definition of this field.
|
||||
|
||||
"""
|
||||
if sys.version_info < (2, 4):
|
||||
# earlier versions did not support padding character
|
||||
_name = self.name[:11] + "\0" * (11 - len(self.name))
|
||||
else:
|
||||
_name = self.name.ljust(11, '\0')
|
||||
return (
|
||||
_name +
|
||||
self.typeCode +
|
||||
#data address
|
||||
chr(0) * 4 +
|
||||
chr(self.length) +
|
||||
chr(self.decimalCount) +
|
||||
chr(0) * 14
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return "%-10s %1s %3d %3d" % self.fieldInfo()
|
||||
|
||||
def fieldInfo(self):
|
||||
"""Return field information.
|
||||
|
||||
Return:
|
||||
Return value is a (name, type, length, decimals) tuple.
|
||||
|
||||
"""
|
||||
return (self.name, self.typeCode, self.length, self.decimalCount)
|
||||
|
||||
def rawFromRecord(self, record):
|
||||
"""Return a "raw" field value from the record string."""
|
||||
return record[self.start:self.end]
|
||||
|
||||
def decodeFromRecord(self, record):
|
||||
"""Return decoded field value from the record string."""
|
||||
try:
|
||||
return self.decodeValue(self.rawFromRecord(record))
|
||||
except:
|
||||
if self.ignoreErrors:
|
||||
return utils.INVALID_VALUE
|
||||
else:
|
||||
raise
|
||||
|
||||
def decodeValue(self, value):
|
||||
"""Return decoded value from string value.
|
||||
|
||||
This method shouldn't be used publicly. It's called from the
|
||||
`decodeFromRecord` method.
|
||||
|
||||
This is an abstract method and it must be overridden in child classes.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def encodeValue(self, value):
|
||||
"""Return str object containing encoded field value.
|
||||
|
||||
This is an abstract method and it must be overridden in child classes.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
## real classes
|
||||
|
||||
class DbfCharacterFieldDef(DbfFieldDef):
|
||||
"""Definition of the character field."""
|
||||
|
||||
typeCode = "C"
|
||||
defaultValue = b''
|
||||
|
||||
def decodeValue(self, value):
|
||||
"""Return string object.
|
||||
|
||||
Return value is a ``value`` argument with stripped right spaces.
|
||||
|
||||
"""
|
||||
return value.rstrip(b' ').decode('utf-8')
|
||||
|
||||
def encodeValue(self, value):
|
||||
"""Return raw data string encoded from a ``value``."""
|
||||
return str(value)[:self.length].ljust(self.length)
|
||||
|
||||
|
||||
class DbfNumericFieldDef(DbfFieldDef):
|
||||
"""Definition of the numeric field."""
|
||||
|
||||
typeCode = "N"
|
||||
# XXX: now I'm not sure it was a good idea to make a class field
|
||||
# `defaultValue` instead of a generic method as it was implemented
|
||||
# previously -- it's ok with all types except number, cuz
|
||||
# if self.decimalCount is 0, we should return 0 and 0.0 otherwise.
|
||||
defaultValue = 0
|
||||
|
||||
def decodeValue(self, value):
|
||||
"""Return a number decoded from ``value``.
|
||||
|
||||
If decimals is zero, value will be decoded as an integer;
|
||||
or as a float otherwise.
|
||||
|
||||
Return:
|
||||
Return value is a int (long) or float instance.
|
||||
|
||||
"""
|
||||
value = value.strip(b' \0')
|
||||
if b'.' in value:
|
||||
# a float (has decimal separator)
|
||||
return float(value)
|
||||
elif value:
|
||||
# must be an integer
|
||||
return int(value)
|
||||
else:
|
||||
return 0
|
||||
|
||||
def encodeValue(self, value):
|
||||
"""Return string containing encoded ``value``."""
|
||||
_rv = ("%*.*f" % (self.length, self.decimalCount, value))
|
||||
if len(_rv) > self.length:
|
||||
_ppos = _rv.find(".")
|
||||
if 0 <= _ppos <= self.length:
|
||||
_rv = _rv[:self.length]
|
||||
else:
|
||||
raise ValueError("[%s] Numeric overflow: %s (field width: %i)"
|
||||
% (self.name, _rv, self.length))
|
||||
return _rv
|
||||
|
||||
class DbfFloatFieldDef(DbfNumericFieldDef):
|
||||
"""Definition of the float field - same as numeric."""
|
||||
|
||||
typeCode = "F"
|
||||
|
||||
class DbfIntegerFieldDef(DbfFieldDef):
|
||||
"""Definition of the integer field."""
|
||||
|
||||
typeCode = "I"
|
||||
length = 4
|
||||
defaultValue = 0
|
||||
|
||||
def decodeValue(self, value):
|
||||
"""Return an integer number decoded from ``value``."""
|
||||
return struct.unpack("<i", value)[0]
|
||||
|
||||
def encodeValue(self, value):
|
||||
"""Return string containing encoded ``value``."""
|
||||
return struct.pack("<i", int(value))
|
||||
|
||||
class DbfCurrencyFieldDef(DbfFieldDef):
|
||||
"""Definition of the currency field."""
|
||||
|
||||
typeCode = "Y"
|
||||
length = 8
|
||||
defaultValue = 0.0
|
||||
|
||||
def decodeValue(self, value):
|
||||
"""Return float number decoded from ``value``."""
|
||||
return struct.unpack("<q", value)[0] / 10000.
|
||||
|
||||
def encodeValue(self, value):
|
||||
"""Return string containing encoded ``value``."""
|
||||
return struct.pack("<q", round(value * 10000))
|
||||
|
||||
class DbfLogicalFieldDef(DbfFieldDef):
|
||||
"""Definition of the logical field."""
|
||||
|
||||
typeCode = "L"
|
||||
defaultValue = -1
|
||||
length = 1
|
||||
|
||||
def decodeValue(self, value):
|
||||
"""Return True, False or -1 decoded from ``value``."""
|
||||
# Note: value always is 1-char string
|
||||
if value == "?":
|
||||
return -1
|
||||
if value in "NnFf ":
|
||||
return False
|
||||
if value in "YyTt":
|
||||
return True
|
||||
raise ValueError("[%s] Invalid logical value %r" % (self.name, value))
|
||||
|
||||
def encodeValue(self, value):
|
||||
"""Return a character from the "TF?" set.
|
||||
|
||||
Return:
|
||||
Return value is "T" if ``value`` is True
|
||||
"?" if value is -1 or False otherwise.
|
||||
|
||||
"""
|
||||
if value is True:
|
||||
return "T"
|
||||
if value == -1:
|
||||
return "?"
|
||||
return "F"
|
||||
|
||||
|
||||
class DbfMemoFieldDef(DbfFieldDef):
|
||||
"""Definition of the memo field.
|
||||
|
||||
Note: memos aren't currently completely supported.
|
||||
|
||||
"""
|
||||
|
||||
typeCode = "M"
|
||||
defaultValue = " " * 10
|
||||
length = 10
|
||||
|
||||
def decodeValue(self, value):
|
||||
"""Return int .dbt block number decoded from the string object."""
|
||||
#return int(value)
|
||||
raise NotImplementedError
|
||||
|
||||
def encodeValue(self, value):
|
||||
"""Return raw data string encoded from a ``value``.
|
||||
|
||||
Note: this is an internal method.
|
||||
|
||||
"""
|
||||
#return str(value)[:self.length].ljust(self.length)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class DbfDateFieldDef(DbfFieldDef):
|
||||
"""Definition of the date field."""
|
||||
|
||||
typeCode = "D"
|
||||
defaultValue = utils.classproperty(lambda cls: datetime.date.today())
|
||||
# "yyyymmdd" gives us 8 characters
|
||||
length = 8
|
||||
|
||||
def decodeValue(self, value):
|
||||
"""Return a ``datetime.date`` instance decoded from ``value``."""
|
||||
if value.strip():
|
||||
return utils.getDate(value)
|
||||
else:
|
||||
return None
|
||||
|
||||
def encodeValue(self, value):
|
||||
"""Return a string-encoded value.
|
||||
|
||||
``value`` argument should be a value suitable for the
|
||||
`utils.getDate` call.
|
||||
|
||||
Return:
|
||||
Return value is a string in format "yyyymmdd".
|
||||
|
||||
"""
|
||||
if value:
|
||||
return utils.getDate(value).strftime("%Y%m%d")
|
||||
else:
|
||||
return " " * self.length
|
||||
|
||||
|
||||
class DbfDateTimeFieldDef(DbfFieldDef):
|
||||
"""Definition of the timestamp field."""
|
||||
|
||||
# a difference between JDN (Julian Day Number)
|
||||
# and GDN (Gregorian Day Number). note, that GDN < JDN
|
||||
JDN_GDN_DIFF = 1721425
|
||||
typeCode = "T"
|
||||
defaultValue = utils.classproperty(lambda cls: datetime.datetime.now())
|
||||
# two 32-bits integers representing JDN and amount of
|
||||
# milliseconds respectively gives us 8 bytes.
|
||||
# note, that values must be encoded in LE byteorder.
|
||||
length = 8
|
||||
|
||||
def decodeValue(self, value):
|
||||
"""Return a `datetime.datetime` instance."""
|
||||
assert len(value) == self.length
|
||||
# LE byteorder
|
||||
_jdn, _msecs = struct.unpack("<2I", value)
|
||||
if _jdn >= 1:
|
||||
_rv = datetime.datetime.fromordinal(_jdn - self.JDN_GDN_DIFF)
|
||||
_rv += datetime.timedelta(0, _msecs / 1000.0)
|
||||
else:
|
||||
# empty date
|
||||
_rv = None
|
||||
return _rv
|
||||
|
||||
def encodeValue(self, value):
|
||||
"""Return a string-encoded ``value``."""
|
||||
if value:
|
||||
value = utils.getDateTime(value)
|
||||
# LE byteorder
|
||||
_rv = struct.pack("<2I", value.toordinal() + self.JDN_GDN_DIFF,
|
||||
(value.hour * 3600 + value.minute * 60 + value.second) * 1000)
|
||||
else:
|
||||
_rv = "\0" * self.length
|
||||
assert len(_rv) == self.length
|
||||
return _rv
|
||||
|
||||
|
||||
_fieldsRegistry = {}
|
||||
|
||||
def registerField(fieldCls):
|
||||
"""Register field definition class.
|
||||
|
||||
``fieldCls`` should be subclass of the `DbfFieldDef`.
|
||||
|
||||
Use `lookupFor` to retrieve field definition class
|
||||
by the type code.
|
||||
|
||||
"""
|
||||
assert fieldCls.typeCode is not None, "Type code isn't defined"
|
||||
# XXX: use fieldCls.typeCode.upper()? in case of any decign
|
||||
# don't forget to look to the same comment in ``lookupFor`` method
|
||||
_fieldsRegistry[fieldCls.typeCode] = fieldCls
|
||||
|
||||
|
||||
def lookupFor(typeCode):
|
||||
"""Return field definition class for the given type code.
|
||||
|
||||
``typeCode`` must be a single character. That type should be
|
||||
previously registered.
|
||||
|
||||
Use `registerField` to register new field class.
|
||||
|
||||
Return:
|
||||
Return value is a subclass of the `DbfFieldDef`.
|
||||
|
||||
"""
|
||||
# XXX: use typeCode.upper()? in case of any decign don't
|
||||
# forget to look to the same comment in ``registerField``
|
||||
return _fieldsRegistry[chr(typeCode)]
|
||||
|
||||
## register generic types
|
||||
|
||||
for (_name, _val) in list(globals().items()):
|
||||
if isinstance(_val, type) and issubclass(_val, DbfFieldDef) \
|
||||
and (_name != "DbfFieldDef"):
|
||||
__all__.append(_name)
|
||||
registerField(_val)
|
||||
del _name, _val
|
||||
|
||||
# vim: et sts=4 sw=4 :
|
||||
@@ -1,273 +0,0 @@
|
||||
"""DBF header definition.
|
||||
|
||||
TODO:
|
||||
- handle encoding of the character fields
|
||||
(encoding information stored in the DBF header)
|
||||
|
||||
"""
|
||||
"""History (most recent first):
|
||||
16-sep-2010 [als] fromStream: fix century of the last update field
|
||||
11-feb-2007 [als] added .ignoreErrors
|
||||
10-feb-2007 [als] added __getitem__: return field definitions
|
||||
by field name or field number (zero-based)
|
||||
04-jul-2006 [als] added export declaration
|
||||
15-dec-2005 [yc] created
|
||||
"""
|
||||
|
||||
__version__ = "$Revision: 1.6 $"[11:-2]
|
||||
__date__ = "$Date: 2010/09/16 05:06:39 $"[7:-2]
|
||||
|
||||
__all__ = ["DbfHeader"]
|
||||
|
||||
import io
|
||||
import datetime
|
||||
import struct
|
||||
import time
|
||||
import sys
|
||||
|
||||
from . import fields
|
||||
from .utils import getDate
|
||||
|
||||
|
||||
class DbfHeader(object):
|
||||
"""Dbf header definition.
|
||||
|
||||
For more information about dbf header format visit
|
||||
`http://www.clicketyclick.dk/databases/xbase/format/dbf.html#DBF_STRUCT`
|
||||
|
||||
Examples:
|
||||
Create an empty dbf header and add some field definitions:
|
||||
dbfh = DbfHeader()
|
||||
dbfh.addField(("name", "C", 10))
|
||||
dbfh.addField(("date", "D"))
|
||||
dbfh.addField(DbfNumericFieldDef("price", 5, 2))
|
||||
Create a dbf header with field definitions:
|
||||
dbfh = DbfHeader([
|
||||
("name", "C", 10),
|
||||
("date", "D"),
|
||||
DbfNumericFieldDef("price", 5, 2),
|
||||
])
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ("signature", "fields", "lastUpdate", "recordLength",
|
||||
"recordCount", "headerLength", "changed", "_ignore_errors")
|
||||
|
||||
## instance construction and initialization methods
|
||||
|
||||
def __init__(self, fields=None, headerLength=0, recordLength=0,
|
||||
recordCount=0, signature=0x03, lastUpdate=None, ignoreErrors=False,
|
||||
):
|
||||
"""Initialize instance.
|
||||
|
||||
Arguments:
|
||||
fields:
|
||||
a list of field definitions;
|
||||
recordLength:
|
||||
size of the records;
|
||||
headerLength:
|
||||
size of the header;
|
||||
recordCount:
|
||||
number of records stored in DBF;
|
||||
signature:
|
||||
version number (aka signature). using 0x03 as a default meaning
|
||||
"File without DBT". for more information about this field visit
|
||||
``http://www.clicketyclick.dk/databases/xbase/format/dbf.html#DBF_NOTE_1_TARGET``
|
||||
lastUpdate:
|
||||
date of the DBF's update. this could be a string ('yymmdd' or
|
||||
'yyyymmdd'), timestamp (int or float), datetime/date value,
|
||||
a sequence (assuming (yyyy, mm, dd, ...)) or an object having
|
||||
callable ``ticks`` field.
|
||||
ignoreErrors:
|
||||
error processing mode for DBF fields (boolean)
|
||||
|
||||
"""
|
||||
self.signature = signature
|
||||
if fields is None:
|
||||
self.fields = []
|
||||
else:
|
||||
self.fields = list(fields)
|
||||
self.lastUpdate = getDate(lastUpdate)
|
||||
self.recordLength = recordLength
|
||||
self.headerLength = headerLength
|
||||
self.recordCount = recordCount
|
||||
self.ignoreErrors = ignoreErrors
|
||||
# XXX: I'm not sure this is safe to
|
||||
# initialize `self.changed` in this way
|
||||
self.changed = bool(self.fields)
|
||||
|
||||
# @classmethod
|
||||
def fromString(cls, string):
|
||||
"""Return header instance from the string object."""
|
||||
return cls.fromStream(io.StringIO(str(string)))
|
||||
fromString = classmethod(fromString)
|
||||
|
||||
# @classmethod
|
||||
def fromStream(cls, stream):
|
||||
"""Return header object from the stream."""
|
||||
stream.seek(0)
|
||||
first_32 = stream.read(32)
|
||||
if type(first_32) != bytes:
|
||||
_data = bytes(first_32, sys.getfilesystemencoding())
|
||||
_data = first_32
|
||||
(_cnt, _hdrLen, _recLen) = struct.unpack("<I2H", _data[4:12])
|
||||
#reserved = _data[12:32]
|
||||
_year = _data[1]
|
||||
if _year < 80:
|
||||
# dBase II started at 1980. It is quite unlikely
|
||||
# that actual last update date is before that year.
|
||||
_year += 2000
|
||||
else:
|
||||
_year += 1900
|
||||
## create header object
|
||||
_obj = cls(None, _hdrLen, _recLen, _cnt, _data[0],
|
||||
(_year, _data[2], _data[3]))
|
||||
## append field definitions
|
||||
# position 0 is for the deletion flag
|
||||
_pos = 1
|
||||
_data = stream.read(1)
|
||||
while _data != b'\r':
|
||||
_data += stream.read(31)
|
||||
_fld = fields.lookupFor(_data[11]).fromString(_data, _pos)
|
||||
_obj._addField(_fld)
|
||||
_pos = _fld.end
|
||||
_data = stream.read(1)
|
||||
return _obj
|
||||
fromStream = classmethod(fromStream)
|
||||
|
||||
## properties
|
||||
|
||||
year = property(lambda self: self.lastUpdate.year)
|
||||
month = property(lambda self: self.lastUpdate.month)
|
||||
day = property(lambda self: self.lastUpdate.day)
|
||||
|
||||
def ignoreErrors(self, value):
|
||||
"""Update `ignoreErrors` flag on self and all fields"""
|
||||
self._ignore_errors = value = bool(value)
|
||||
for _field in self.fields:
|
||||
_field.ignoreErrors = value
|
||||
ignoreErrors = property(
|
||||
lambda self: self._ignore_errors,
|
||||
ignoreErrors,
|
||||
doc="""Error processing mode for DBF field value conversion
|
||||
|
||||
if set, failing field value conversion will return
|
||||
``INVALID_VALUE`` instead of raising conversion error.
|
||||
|
||||
""")
|
||||
|
||||
## object representation
|
||||
|
||||
def __repr__(self):
|
||||
_rv = """\
|
||||
Version (signature): 0x%02x
|
||||
Last update: %s
|
||||
Header length: %d
|
||||
Record length: %d
|
||||
Record count: %d
|
||||
FieldName Type Len Dec
|
||||
""" % (self.signature, self.lastUpdate, self.headerLength,
|
||||
self.recordLength, self.recordCount)
|
||||
_rv += "\n".join(
|
||||
["%10s %4s %3s %3s" % _fld.fieldInfo() for _fld in self.fields]
|
||||
)
|
||||
return _rv
|
||||
|
||||
## internal methods
|
||||
|
||||
def _addField(self, *defs):
|
||||
"""Internal variant of the `addField` method.
|
||||
|
||||
This method doesn't set `self.changed` field to True.
|
||||
|
||||
Return value is a length of the appended records.
|
||||
Note: this method doesn't modify ``recordLength`` and
|
||||
``headerLength`` fields. Use `addField` instead of this
|
||||
method if you don't exactly know what you're doing.
|
||||
|
||||
"""
|
||||
# insure we have dbf.DbfFieldDef instances first (instantiation
|
||||
# from the tuple could raise an error, in such a case I don't
|
||||
# wanna add any of the definitions -- all will be ignored)
|
||||
_defs = []
|
||||
_recordLength = 0
|
||||
for _def in defs:
|
||||
if isinstance(_def, fields.DbfFieldDef):
|
||||
_obj = _def
|
||||
else:
|
||||
(_name, _type, _len, _dec) = (tuple(_def) + (None,) * 4)[:4]
|
||||
_cls = fields.lookupFor(_type)
|
||||
_obj = _cls(_name, _len, _dec,
|
||||
ignoreErrors=self._ignore_errors)
|
||||
_recordLength += _obj.length
|
||||
_defs.append(_obj)
|
||||
# and now extend field definitions and
|
||||
# update record length
|
||||
self.fields += _defs
|
||||
return _recordLength
|
||||
|
||||
## interface methods
|
||||
|
||||
def addField(self, *defs):
|
||||
"""Add field definition to the header.
|
||||
|
||||
Examples:
|
||||
dbfh.addField(
|
||||
("name", "C", 20),
|
||||
dbf.DbfCharacterFieldDef("surname", 20),
|
||||
dbf.DbfDateFieldDef("birthdate"),
|
||||
("member", "L"),
|
||||
)
|
||||
dbfh.addField(("price", "N", 5, 2))
|
||||
dbfh.addField(dbf.DbfNumericFieldDef("origprice", 5, 2))
|
||||
|
||||
"""
|
||||
_oldLen = self.recordLength
|
||||
self.recordLength += self._addField(*defs)
|
||||
if not _oldLen:
|
||||
self.recordLength += 1
|
||||
# XXX: may be just use:
|
||||
# self.recordeLength += self._addField(*defs) + bool(not _oldLen)
|
||||
# recalculate headerLength
|
||||
self.headerLength = 32 + (32 * len(self.fields)) + 1
|
||||
self.changed = True
|
||||
|
||||
def write(self, stream):
|
||||
"""Encode and write header to the stream."""
|
||||
stream.seek(0)
|
||||
stream.write(self.toString())
|
||||
fields = [_fld.toString() for _fld in self.fields]
|
||||
stream.write(''.join(fields).encode(sys.getfilesystemencoding()))
|
||||
stream.write(b'\x0D') # cr at end of all header data
|
||||
self.changed = False
|
||||
|
||||
def toString(self):
|
||||
"""Returned 32 chars length string with encoded header."""
|
||||
return struct.pack("<4BI2H",
|
||||
self.signature,
|
||||
self.year - 1900,
|
||||
self.month,
|
||||
self.day,
|
||||
self.recordCount,
|
||||
self.headerLength,
|
||||
self.recordLength) + (b'\x00' * 20)
|
||||
#TODO: figure out if bytes(utf-8) is correct here.
|
||||
|
||||
def setCurrentDate(self):
|
||||
"""Update ``self.lastUpdate`` field with current date value."""
|
||||
self.lastUpdate = datetime.date.today()
|
||||
|
||||
def __getitem__(self, item):
|
||||
"""Return a field definition by numeric index or name string"""
|
||||
if isinstance(item, str):
|
||||
_name = item.upper()
|
||||
for _field in self.fields:
|
||||
if _field.name == _name:
|
||||
return _field
|
||||
else:
|
||||
raise KeyError(item)
|
||||
else:
|
||||
# item must be field index
|
||||
return self.fields[item]
|
||||
|
||||
# vim: et sts=4 sw=4 :
|
||||
@@ -1,266 +0,0 @@
|
||||
"""DBF record definition.
|
||||
|
||||
"""
|
||||
"""History (most recent first):
|
||||
11-feb-2007 [als] __repr__: added special case for invalid field values
|
||||
10-feb-2007 [als] added .rawFromStream()
|
||||
30-oct-2006 [als] fix record length in .fromStream()
|
||||
04-jul-2006 [als] added export declaration
|
||||
20-dec-2005 [yc] DbfRecord.write() -> DbfRecord._write();
|
||||
added delete() method.
|
||||
16-dec-2005 [yc] record definition moved from `dbf`.
|
||||
"""
|
||||
|
||||
__version__ = "$Revision: 1.7 $"[11:-2]
|
||||
__date__ = "$Date: 2007/02/11 09:05:49 $"[7:-2]
|
||||
|
||||
__all__ = ["DbfRecord"]
|
||||
|
||||
import sys
|
||||
|
||||
from . import utils
|
||||
|
||||
class DbfRecord(object):
|
||||
"""DBF record.
|
||||
|
||||
Instances of this class shouldn't be created manually,
|
||||
use `dbf.Dbf.newRecord` instead.
|
||||
|
||||
Class implements mapping/sequence interface, so
|
||||
fields could be accessed via their names or indexes
|
||||
(names is a preferred way to access fields).
|
||||
|
||||
Hint:
|
||||
Use `store` method to save modified record.
|
||||
|
||||
Examples:
|
||||
Add new record to the database:
|
||||
db = Dbf(filename)
|
||||
rec = db.newRecord()
|
||||
rec["FIELD1"] = value1
|
||||
rec["FIELD2"] = value2
|
||||
rec.store()
|
||||
Or the same, but modify existed
|
||||
(second in this case) record:
|
||||
db = Dbf(filename)
|
||||
rec = db[2]
|
||||
rec["FIELD1"] = value1
|
||||
rec["FIELD2"] = value2
|
||||
rec.store()
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = "dbf", "index", "deleted", "fieldData"
|
||||
|
||||
## creation and initialization
|
||||
|
||||
def __init__(self, dbf, index=None, deleted=False, data=None):
|
||||
"""Instance initialization.
|
||||
|
||||
Arguments:
|
||||
dbf:
|
||||
A `Dbf.Dbf` instance this record belonogs to.
|
||||
index:
|
||||
An integer record index or None. If this value is
|
||||
None, record will be appended to the DBF.
|
||||
deleted:
|
||||
Boolean flag indicating whether this record
|
||||
is a deleted record.
|
||||
data:
|
||||
A sequence or None. This is a data of the fields.
|
||||
If this argument is None, default values will be used.
|
||||
|
||||
"""
|
||||
self.dbf = dbf
|
||||
# XXX: I'm not sure ``index`` is necessary
|
||||
self.index = index
|
||||
self.deleted = deleted
|
||||
if data is None:
|
||||
self.fieldData = [_fd.defaultValue for _fd in dbf.header.fields]
|
||||
else:
|
||||
self.fieldData = list(data)
|
||||
|
||||
# XXX: validate self.index before calculating position?
|
||||
position = property(lambda self: self.dbf.header.headerLength + \
|
||||
self.index * self.dbf.header.recordLength)
|
||||
|
||||
def rawFromStream(cls, dbf, index):
|
||||
"""Return raw record contents read from the stream.
|
||||
|
||||
Arguments:
|
||||
dbf:
|
||||
A `Dbf.Dbf` instance containing the record.
|
||||
index:
|
||||
Index of the record in the records' container.
|
||||
This argument can't be None in this call.
|
||||
|
||||
Return value is a string containing record data in DBF format.
|
||||
|
||||
"""
|
||||
# XXX: may be write smth assuming, that current stream
|
||||
# position is the required one? it could save some
|
||||
# time required to calculate where to seek in the file
|
||||
dbf.stream.seek(dbf.header.headerLength +
|
||||
index * dbf.header.recordLength)
|
||||
return dbf.stream.read(dbf.header.recordLength)
|
||||
rawFromStream = classmethod(rawFromStream)
|
||||
|
||||
def fromStream(cls, dbf, index):
|
||||
"""Return a record read from the stream.
|
||||
|
||||
Arguments:
|
||||
dbf:
|
||||
A `Dbf.Dbf` instance new record should belong to.
|
||||
index:
|
||||
Index of the record in the records' container.
|
||||
This argument can't be None in this call.
|
||||
|
||||
Return value is an instance of the current class.
|
||||
|
||||
"""
|
||||
return cls.fromString(dbf, cls.rawFromStream(dbf, index), index)
|
||||
fromStream = classmethod(fromStream)
|
||||
|
||||
def fromString(cls, dbf, string, index=None):
|
||||
"""Return record read from the string object.
|
||||
|
||||
Arguments:
|
||||
dbf:
|
||||
A `Dbf.Dbf` instance new record should belong to.
|
||||
string:
|
||||
A string new record should be created from.
|
||||
index:
|
||||
Index of the record in the container. If this
|
||||
argument is None, record will be appended.
|
||||
|
||||
Return value is an instance of the current class.
|
||||
|
||||
"""
|
||||
return cls(dbf, index, string[0]=="*",
|
||||
[_fd.decodeFromRecord(string) for _fd in dbf.header.fields])
|
||||
fromString = classmethod(fromString)
|
||||
|
||||
## object representation
|
||||
|
||||
def __repr__(self):
|
||||
_template = "%%%ds: %%s (%%s)" % max([len(_fld)
|
||||
for _fld in self.dbf.fieldNames])
|
||||
_rv = []
|
||||
for _fld in self.dbf.fieldNames:
|
||||
_val = self[_fld]
|
||||
if _val is utils.INVALID_VALUE:
|
||||
_rv.append(_template %
|
||||
(_fld, "None", "value cannot be decoded"))
|
||||
else:
|
||||
_rv.append(_template % (_fld, _val, type(_val)))
|
||||
return "\n".join(_rv)
|
||||
|
||||
## protected methods
|
||||
|
||||
def _write(self):
|
||||
"""Write data to the dbf stream.
|
||||
|
||||
Note:
|
||||
This isn't a public method, it's better to
|
||||
use 'store' instead publicly.
|
||||
Be design ``_write`` method should be called
|
||||
only from the `Dbf` instance.
|
||||
|
||||
|
||||
"""
|
||||
self._validateIndex(False)
|
||||
self.dbf.stream.seek(self.position)
|
||||
self.dbf.stream.write(bytes(self.toString(),
|
||||
sys.getfilesystemencoding()))
|
||||
# FIXME: may be move this write somewhere else?
|
||||
# why we should check this condition for each record?
|
||||
if self.index == len(self.dbf):
|
||||
# this is the last record,
|
||||
# we should write SUB (ASCII 26)
|
||||
self.dbf.stream.write(b"\x1A")
|
||||
|
||||
## utility methods
|
||||
|
||||
def _validateIndex(self, allowUndefined=True, checkRange=False):
|
||||
"""Valid ``self.index`` value.
|
||||
|
||||
If ``allowUndefined`` argument is True functions does nothing
|
||||
in case of ``self.index`` pointing to None object.
|
||||
|
||||
"""
|
||||
if self.index is None:
|
||||
if not allowUndefined:
|
||||
raise ValueError("Index is undefined")
|
||||
elif self.index < 0:
|
||||
raise ValueError("Index can't be negative (%s)" % self.index)
|
||||
elif checkRange and self.index <= self.dbf.header.recordCount:
|
||||
raise ValueError("There are only %d records in the DBF" %
|
||||
self.dbf.header.recordCount)
|
||||
|
||||
## interface methods
|
||||
|
||||
def store(self):
|
||||
"""Store current record in the DBF.
|
||||
|
||||
If ``self.index`` is None, this record will be appended to the
|
||||
records of the DBF this records belongs to; or replaced otherwise.
|
||||
|
||||
"""
|
||||
self._validateIndex()
|
||||
if self.index is None:
|
||||
self.index = len(self.dbf)
|
||||
self.dbf.append(self)
|
||||
else:
|
||||
self.dbf[self.index] = self
|
||||
|
||||
def delete(self):
|
||||
"""Mark method as deleted."""
|
||||
self.deleted = True
|
||||
|
||||
def toString(self):
|
||||
"""Return string packed record values."""
|
||||
# for (_def, _dat) in zip(self.dbf.header.fields, self.fieldData):
|
||||
#
|
||||
|
||||
return "".join([" *"[self.deleted]] + [
|
||||
_def.encodeValue(_dat)
|
||||
for (_def, _dat) in zip(self.dbf.header.fields, self.fieldData)
|
||||
])
|
||||
|
||||
def asList(self):
|
||||
"""Return a flat list of fields.
|
||||
|
||||
Note:
|
||||
Change of the list's values won't change
|
||||
real values stored in this object.
|
||||
|
||||
"""
|
||||
return self.fieldData[:]
|
||||
|
||||
def asDict(self):
|
||||
"""Return a dictionary of fields.
|
||||
|
||||
Note:
|
||||
Change of the dicts's values won't change
|
||||
real values stored in this object.
|
||||
|
||||
"""
|
||||
return dict([_i for _i in zip(self.dbf.fieldNames, self.fieldData)])
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Return value by field name or field index."""
|
||||
if isinstance(key, int):
|
||||
# integer index of the field
|
||||
return self.fieldData[key]
|
||||
# assuming string field name
|
||||
return self.fieldData[self.dbf.indexOfFieldName(key)]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"""Set field value by integer index of the field or string name."""
|
||||
if isinstance(key, int):
|
||||
# integer index of the field
|
||||
return self.fieldData[key]
|
||||
# assuming string field name
|
||||
self.fieldData[self.dbf.indexOfFieldName(key)] = value
|
||||
|
||||
# vim: et sts=4 sw=4 :
|
||||
@@ -1,170 +0,0 @@
|
||||
"""String utilities.
|
||||
|
||||
TODO:
|
||||
- allow strings in getDateTime routine;
|
||||
"""
|
||||
"""History (most recent first):
|
||||
11-feb-2007 [als] added INVALID_VALUE
|
||||
10-feb-2007 [als] allow date strings padded with spaces instead of zeroes
|
||||
20-dec-2005 [yc] handle long objects in getDate/getDateTime
|
||||
16-dec-2005 [yc] created from ``strutil`` module.
|
||||
"""
|
||||
|
||||
__version__ = "$Revision: 1.4 $"[11:-2]
|
||||
__date__ = "$Date: 2007/02/11 08:57:17 $"[7:-2]
|
||||
|
||||
import datetime
|
||||
import time
|
||||
|
||||
|
||||
def unzfill(str):
|
||||
"""Return a string without ASCII NULs.
|
||||
|
||||
This function searchers for the first NUL (ASCII 0) occurrence
|
||||
and truncates string till that position.
|
||||
|
||||
"""
|
||||
try:
|
||||
return str[:str.index(b'\0')]
|
||||
except ValueError:
|
||||
return str
|
||||
|
||||
|
||||
def getDate(date=None):
|
||||
"""Return `datetime.date` instance.
|
||||
|
||||
Type of the ``date`` argument could be one of the following:
|
||||
None:
|
||||
use current date value;
|
||||
datetime.date:
|
||||
this value will be returned;
|
||||
datetime.datetime:
|
||||
the result of the date.date() will be returned;
|
||||
string:
|
||||
assuming "%Y%m%d" or "%y%m%dd" format;
|
||||
number:
|
||||
assuming it's a timestamp (returned for example
|
||||
by the time.time() call;
|
||||
sequence:
|
||||
assuming (year, month, day, ...) sequence;
|
||||
|
||||
Additionally, if ``date`` has callable ``ticks`` attribute,
|
||||
it will be used and result of the called would be treated
|
||||
as a timestamp value.
|
||||
|
||||
"""
|
||||
if date is None:
|
||||
# use current value
|
||||
return datetime.date.today()
|
||||
if isinstance(date, datetime.date):
|
||||
return date
|
||||
if isinstance(date, datetime.datetime):
|
||||
return date.date()
|
||||
if isinstance(date, (int, float)):
|
||||
# date is a timestamp
|
||||
return datetime.date.fromtimestamp(date)
|
||||
if isinstance(date, str):
|
||||
date = date.replace(" ", "0")
|
||||
if len(date) == 6:
|
||||
# yymmdd
|
||||
return datetime.date(*time.strptime(date, "%y%m%d")[:3])
|
||||
# yyyymmdd
|
||||
return datetime.date(*time.strptime(date, "%Y%m%d")[:3])
|
||||
if hasattr(date, "__getitem__"):
|
||||
# a sequence (assuming date/time tuple)
|
||||
return datetime.date(*date[:3])
|
||||
return datetime.date.fromtimestamp(date.ticks())
|
||||
|
||||
|
||||
def getDateTime(value=None):
|
||||
"""Return `datetime.datetime` instance.
|
||||
|
||||
Type of the ``value`` argument could be one of the following:
|
||||
None:
|
||||
use current date value;
|
||||
datetime.date:
|
||||
result will be converted to the `datetime.datetime` instance
|
||||
using midnight;
|
||||
datetime.datetime:
|
||||
``value`` will be returned as is;
|
||||
string:
|
||||
*** CURRENTLY NOT SUPPORTED ***;
|
||||
number:
|
||||
assuming it's a timestamp (returned for example
|
||||
by the time.time() call;
|
||||
sequence:
|
||||
assuming (year, month, day, ...) sequence;
|
||||
|
||||
Additionally, if ``value`` has callable ``ticks`` attribute,
|
||||
it will be used and result of the called would be treated
|
||||
as a timestamp value.
|
||||
|
||||
"""
|
||||
if value is None:
|
||||
# use current value
|
||||
return datetime.datetime.today()
|
||||
if isinstance(value, datetime.datetime):
|
||||
return value
|
||||
if isinstance(value, datetime.date):
|
||||
return datetime.datetime.fromordinal(value.toordinal())
|
||||
if isinstance(value, (int, float)):
|
||||
# value is a timestamp
|
||||
return datetime.datetime.fromtimestamp(value)
|
||||
if isinstance(value, str):
|
||||
raise NotImplementedError("Strings aren't currently implemented")
|
||||
if hasattr(value, "__getitem__"):
|
||||
# a sequence (assuming date/time tuple)
|
||||
return datetime.datetime(*tuple(value)[:6])
|
||||
return datetime.datetime.fromtimestamp(value.ticks())
|
||||
|
||||
|
||||
class classproperty(property):
|
||||
"""Works in the same way as a ``property``, but for the classes."""
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
return self.fget(cls)
|
||||
|
||||
|
||||
class _InvalidValue(object):
|
||||
|
||||
"""Value returned from DBF records when field validation fails
|
||||
|
||||
The value is not equal to anything except for itself
|
||||
and equal to all empty values: None, 0, empty string etc.
|
||||
In other words, invalid value is equal to None and not equal
|
||||
to None at the same time.
|
||||
|
||||
This value yields zero upon explicit conversion to a number type,
|
||||
empty string for string types, and False for boolean.
|
||||
|
||||
"""
|
||||
|
||||
def __eq__(self, other):
|
||||
return not other
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (other is self)
|
||||
|
||||
def __bool__(self):
|
||||
return False
|
||||
|
||||
def __int__(self):
|
||||
return 0
|
||||
__long__ = __int__
|
||||
|
||||
def __float__(self):
|
||||
return 0.0
|
||||
|
||||
def __str__(self):
|
||||
return ""
|
||||
|
||||
def __unicode__(self):
|
||||
return ""
|
||||
|
||||
def __repr__(self):
|
||||
return "<INVALID>"
|
||||
|
||||
# invalid value is a constant singleton
|
||||
INVALID_VALUE = _InvalidValue()
|
||||
|
||||
# vim: set et sts=4 sw=4 :
|
||||
@@ -1,24 +0,0 @@
|
||||
from __future__ import division
|
||||
|
||||
|
||||
def median(data):
|
||||
"""
|
||||
Return the median (middle value) of numeric data, using the common
|
||||
"mean of middle two" method. If data is empty, ValueError is raised.
|
||||
|
||||
Mimics the behaviour of Python3's statistics.median
|
||||
|
||||
>>> median([1, 3, 5])
|
||||
3
|
||||
>>> median([1, 3, 5, 7])
|
||||
4.0
|
||||
|
||||
"""
|
||||
data = sorted(data)
|
||||
n = len(data)
|
||||
if not n:
|
||||
raise ValueError("No median for empty data")
|
||||
i = n // 2
|
||||
if n % 2:
|
||||
return data[i]
|
||||
return (data[i - 1] + data[i]) / 2
|
||||
@@ -1,6 +1,5 @@
|
||||
pytest
|
||||
pytest-cov
|
||||
backports.csv; python_version < '3.0'
|
||||
MarkupPy
|
||||
odfpy
|
||||
openpyxl>=2.4.0
|
||||
|
||||
+9
-20
@@ -1,7 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Tests for Tablib."""
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
import doctest
|
||||
@@ -12,7 +10,6 @@ from uuid import uuid4
|
||||
|
||||
from MarkupPy import markup
|
||||
import tablib
|
||||
from tablib.compat import unicode, is_py3
|
||||
from tablib.core import Row, detect_format
|
||||
from tablib.formats import _csv as csv_module
|
||||
|
||||
@@ -251,10 +248,7 @@ class TablibTestCase(BaseTestCase):
|
||||
def test_unicode_append(self):
|
||||
"""Passes in a single unicode character and exports."""
|
||||
|
||||
if is_py3:
|
||||
new_row = ('å', 'é')
|
||||
else:
|
||||
exec ("new_row = (u'å', u'é')")
|
||||
new_row = ('å', 'é')
|
||||
|
||||
data.append(new_row)
|
||||
self._test_export_data_in_all_formats(data)
|
||||
@@ -458,10 +452,10 @@ class TablibTestCase(BaseTestCase):
|
||||
# add another entry to test right field width for
|
||||
# integer
|
||||
self.founders.append(('Old', 'Man', 100500))
|
||||
self.assertEqual('first_name|last_name |gpa ', unicode(self.founders).split('\n')[0])
|
||||
self.assertEqual('first_name|last_name |gpa ', str(self.founders).split('\n')[0])
|
||||
|
||||
def test_databook_add_sheet_accepts_only_dataset_instances(self):
|
||||
class NotDataset(object):
|
||||
class NotDataset:
|
||||
def append(self, item):
|
||||
pass
|
||||
|
||||
@@ -684,10 +678,7 @@ class CSVTests(BaseTestCase):
|
||||
|
||||
data = tablib.Dataset()
|
||||
|
||||
if sys.version_info[0] > 2:
|
||||
data.append(['\xfc', '\xfd'])
|
||||
else:
|
||||
exec ("data.append([u'\xfc', u'\xfd'])")
|
||||
data.append(['\xfc', '\xfd'])
|
||||
|
||||
data.csv
|
||||
|
||||
@@ -698,7 +689,7 @@ class CSVTests(BaseTestCase):
|
||||
data.csv = self.founders.csv
|
||||
|
||||
headers = data.headers
|
||||
self.assertTrue(isinstance(headers[0], unicode))
|
||||
self.assertTrue(isinstance(headers[0], str))
|
||||
|
||||
orig_first_name = self.founders[self.headers[0]]
|
||||
csv_first_name = data[headers[0]]
|
||||
@@ -711,7 +702,7 @@ class CSVTests(BaseTestCase):
|
||||
data.csv = self.founders.csv
|
||||
|
||||
target_header = data.headers[0]
|
||||
self.assertTrue(isinstance(target_header, unicode))
|
||||
self.assertTrue(isinstance(target_header, str))
|
||||
|
||||
del data[target_header]
|
||||
|
||||
@@ -1034,11 +1025,9 @@ class DBFTests(BaseTestCase):
|
||||
_regression_dbf += b' 50.0000000'
|
||||
_regression_dbf += b'\x1a'
|
||||
|
||||
if is_py3:
|
||||
# If in python3, decode regression string to binary.
|
||||
# _regression_dbf = bytes(_regression_dbf, 'utf-8')
|
||||
# _regression_dbf = _regression_dbf.replace(b'\n', b'\r')
|
||||
pass
|
||||
# If in python3, decode regression string to binary.
|
||||
# _regression_dbf = bytes(_regression_dbf, 'utf-8')
|
||||
# _regression_dbf = _regression_dbf.replace(b'\n', b'\r')
|
||||
|
||||
try:
|
||||
self.assertEqual(_regression_dbf, data.dbf)
|
||||
|
||||
Reference in New Issue
Block a user