sqlobject 3.13.1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sqlobject/__init__.py +15 -0
- sqlobject/__version__.py +8 -0
- sqlobject/boundattributes.py +124 -0
- sqlobject/cache.py +376 -0
- sqlobject/classregistry.py +138 -0
- sqlobject/col.py +2187 -0
- sqlobject/compat.py +59 -0
- sqlobject/conftest.py +64 -0
- sqlobject/constraints.py +77 -0
- sqlobject/converters.py +278 -0
- sqlobject/dbconnection.py +1137 -0
- sqlobject/dberrors.py +54 -0
- sqlobject/declarative.py +211 -0
- sqlobject/events.py +365 -0
- sqlobject/firebird/__init__.py +8 -0
- sqlobject/firebird/firebirdconnection.py +463 -0
- sqlobject/include/__init__.py +1 -0
- sqlobject/include/hashcol.py +109 -0
- sqlobject/include/tests/__init__.py +1 -0
- sqlobject/include/tests/test_hashcol.py +84 -0
- sqlobject/index.py +189 -0
- sqlobject/inheritance/__init__.py +551 -0
- sqlobject/inheritance/iteration.py +106 -0
- sqlobject/inheritance/tests/__init__.py +1 -0
- sqlobject/inheritance/tests/test_aggregates.py +23 -0
- sqlobject/inheritance/tests/test_asdict.py +48 -0
- sqlobject/inheritance/tests/test_deep_inheritance.py +114 -0
- sqlobject/inheritance/tests/test_destroy_cascade.py +23 -0
- sqlobject/inheritance/tests/test_foreignKey.py +94 -0
- sqlobject/inheritance/tests/test_indexes.py +42 -0
- sqlobject/inheritance/tests/test_inheritance.py +164 -0
- sqlobject/inheritance/tests/test_inheritance_tree.py +50 -0
- sqlobject/joins.py +656 -0
- sqlobject/main.py +1880 -0
- sqlobject/manager/__init__.py +1 -0
- sqlobject/manager/command.py +1309 -0
- sqlobject/maxdb/__init__.py +8 -0
- sqlobject/maxdb/maxdbconnection.py +320 -0
- sqlobject/maxdb/readme.txt +27 -0
- sqlobject/mssql/__init__.py +8 -0
- sqlobject/mssql/mssqlconnection.py +417 -0
- sqlobject/mysql/__init__.py +8 -0
- sqlobject/mysql/mysqlconnection.py +562 -0
- sqlobject/postgres/__init__.py +8 -0
- sqlobject/postgres/pgconnection.py +619 -0
- sqlobject/sqlbuilder.py +1655 -0
- sqlobject/sqlite/__init__.py +8 -0
- sqlobject/sqlite/sqliteconnection.py +418 -0
- sqlobject/sresults.py +377 -0
- sqlobject/styles.py +174 -0
- sqlobject/sybase/__init__.py +8 -0
- sqlobject/sybase/sybaseconnection.py +183 -0
- sqlobject/tests/__init__.py +1 -0
- sqlobject/tests/dbtest.py +363 -0
- sqlobject/tests/test_ForeignKey.py +133 -0
- sqlobject/tests/test_ForeignKey_cascade.py +150 -0
- sqlobject/tests/test_NoneValuedResultItem.py +33 -0
- sqlobject/tests/test_SQLMultipleJoin.py +65 -0
- sqlobject/tests/test_SQLRelatedJoin.py +114 -0
- sqlobject/tests/test_SingleJoin.py +30 -0
- sqlobject/tests/test_aggregates.py +75 -0
- sqlobject/tests/test_aliases.py +49 -0
- sqlobject/tests/test_asdict.py +18 -0
- sqlobject/tests/test_auto.py +218 -0
- sqlobject/tests/test_basic.py +359 -0
- sqlobject/tests/test_blob.py +34 -0
- sqlobject/tests/test_boundattributes.py +67 -0
- sqlobject/tests/test_cache.py +57 -0
- sqlobject/tests/test_class_hash.py +22 -0
- sqlobject/tests/test_columns_order.py +18 -0
- sqlobject/tests/test_combining_joins.py +47 -0
- sqlobject/tests/test_comparison.py +24 -0
- sqlobject/tests/test_compat.py +10 -0
- sqlobject/tests/test_complex_sorting.py +77 -0
- sqlobject/tests/test_constraints.py +43 -0
- sqlobject/tests/test_converters.py +276 -0
- sqlobject/tests/test_create_drop.py +29 -0
- sqlobject/tests/test_csvexport.py +101 -0
- sqlobject/tests/test_csvimport.py +21 -0
- sqlobject/tests/test_cyclic_reference.py +55 -0
- sqlobject/tests/test_datetime.py +171 -0
- sqlobject/tests/test_dbconnection.py +17 -0
- sqlobject/tests/test_decimal.py +63 -0
- sqlobject/tests/test_declarative.py +83 -0
- sqlobject/tests/test_default_style.py +65 -0
- sqlobject/tests/test_delete.py +74 -0
- sqlobject/tests/test_distinct.py +36 -0
- sqlobject/tests/test_empty.py +22 -0
- sqlobject/tests/test_enum.py +75 -0
- sqlobject/tests/test_events.py +142 -0
- sqlobject/tests/test_exceptions.py +40 -0
- sqlobject/tests/test_expire.py +29 -0
- sqlobject/tests/test_groupBy.py +45 -0
- sqlobject/tests/test_identity.py +29 -0
- sqlobject/tests/test_indexes.py +100 -0
- sqlobject/tests/test_inheritance.py +35 -0
- sqlobject/tests/test_joins.py +160 -0
- sqlobject/tests/test_joins_conditional.py +143 -0
- sqlobject/tests/test_jsonbcol.py +53 -0
- sqlobject/tests/test_jsoncol.py +56 -0
- sqlobject/tests/test_lazy.py +165 -0
- sqlobject/tests/test_md5.py +13 -0
- sqlobject/tests/test_mysql.py +50 -0
- sqlobject/tests/test_new_joins.py +165 -0
- sqlobject/tests/test_parse_uri.py +147 -0
- sqlobject/tests/test_paste.py +92 -0
- sqlobject/tests/test_perConnection.py +20 -0
- sqlobject/tests/test_pickle.py +42 -0
- sqlobject/tests/test_picklecol.py +44 -0
- sqlobject/tests/test_postgres.py +82 -0
- sqlobject/tests/test_reparent_sqlmeta.py +33 -0
- sqlobject/tests/test_schema.py +30 -0
- sqlobject/tests/test_select.py +218 -0
- sqlobject/tests/test_select_through.py +63 -0
- sqlobject/tests/test_setters.py +23 -0
- sqlobject/tests/test_slice.py +59 -0
- sqlobject/tests/test_sorting.py +80 -0
- sqlobject/tests/test_sqlbuilder.py +134 -0
- sqlobject/tests/test_sqlbuilder_dbspecific.py +66 -0
- sqlobject/tests/test_sqlbuilder_importproxy.py +55 -0
- sqlobject/tests/test_sqlbuilder_joins_instances.py +90 -0
- sqlobject/tests/test_sqlite.py +160 -0
- sqlobject/tests/test_sqlmeta_idName.py +52 -0
- sqlobject/tests/test_sqlobject_admin.py +38 -0
- sqlobject/tests/test_string_id.py +79 -0
- sqlobject/tests/test_style.py +36 -0
- sqlobject/tests/test_subqueries.py +142 -0
- sqlobject/tests/test_transactions.py +122 -0
- sqlobject/tests/test_unicode.py +84 -0
- sqlobject/tests/test_uuidcol.py +40 -0
- sqlobject/tests/test_validation.py +125 -0
- sqlobject/tests/test_views.py +159 -0
- sqlobject/util/__init__.py +1 -0
- sqlobject/util/csvexport.py +210 -0
- sqlobject/util/csvimport.py +366 -0
- sqlobject/util/moduleloader.py +35 -0
- sqlobject/util/threadinglocal.py +6 -0
- sqlobject/versioning/__init__.py +120 -0
- sqlobject/versioning/test/__init__.py +0 -0
- sqlobject/versioning/test/test_version.py +183 -0
- sqlobject/views.py +151 -0
- sqlobject/wsgi_middleware.py +109 -0
- sqlobject-3.13.1.data/scripts/sqlobject-admin +36 -0
- sqlobject-3.13.1.data/scripts/sqlobject-convertOldURI +19 -0
- sqlobject-3.13.1.dist-info/METADATA +177 -0
- sqlobject-3.13.1.dist-info/RECORD +149 -0
- sqlobject-3.13.1.dist-info/WHEEL +6 -0
- sqlobject-3.13.1.dist-info/entry_points.txt +4 -0
- sqlobject-3.13.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Exports a SQLObject class (possibly annotated) to a CSV file.
|
|
3
|
+
"""
|
|
4
|
+
import csv
|
|
5
|
+
import os
|
|
6
|
+
try:
|
|
7
|
+
from cStringIO import StringIO
|
|
8
|
+
except ImportError:
|
|
9
|
+
try:
|
|
10
|
+
from StringIO import StringIO
|
|
11
|
+
except ImportError:
|
|
12
|
+
from io import StringIO, BytesIO
|
|
13
|
+
import sqlobject
|
|
14
|
+
from sqlobject.compat import PY2, string_type
|
|
15
|
+
|
|
16
|
+
__all__ = ['export_csv', 'export_csv_zip']
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def export_csv(soClass, select=None, writer=None, connection=None,
|
|
20
|
+
orderBy=None):
|
|
21
|
+
"""
|
|
22
|
+
Export the SQLObject class ``soClass`` to a CSV file.
|
|
23
|
+
|
|
24
|
+
``soClass`` can also be a SelectResults object, as returned by
|
|
25
|
+
``.select()``. If it is a class, all objects will be retrieved,
|
|
26
|
+
ordered by ``orderBy`` if given, or the ``.csvOrderBy`` attribute
|
|
27
|
+
if present (but csvOrderBy will only be applied when no select
|
|
28
|
+
result is given).
|
|
29
|
+
|
|
30
|
+
You can also pass in select results (or simply a list of
|
|
31
|
+
instances) in ``select`` -- if you have a list of objects (not a
|
|
32
|
+
SelectResults instance, as produced by ``.select()``) then you must
|
|
33
|
+
pass it in with ``select`` and pass the class in as the first
|
|
34
|
+
argument.
|
|
35
|
+
|
|
36
|
+
``writer`` is a ``csv.writer()`` object, or a file-like object.
|
|
37
|
+
If not given, the string of the file will be returned.
|
|
38
|
+
|
|
39
|
+
Uses ``connection`` as the data source, if given, otherwise the
|
|
40
|
+
default connection.
|
|
41
|
+
|
|
42
|
+
Columns can be annotated with ``.csvTitle`` attributes, which will
|
|
43
|
+
form the attributes of the columns, or 'title' (secondarily), or
|
|
44
|
+
if nothing then the column attribute name.
|
|
45
|
+
|
|
46
|
+
If a column has a ``.noCSV`` attribute which is true, then the
|
|
47
|
+
column will be suppressed.
|
|
48
|
+
|
|
49
|
+
Additionally a class can have an ``.extraCSVColumns`` attribute,
|
|
50
|
+
which should be a list of strings/tuples. If a tuple, it should
|
|
51
|
+
be like ``(attribute, title)``, otherwise it is the attribute,
|
|
52
|
+
which will also be the title. These will be appended to the end
|
|
53
|
+
of the CSV file; the attribute will be retrieved from instances.
|
|
54
|
+
|
|
55
|
+
Also a ``.csvColumnOrder`` attribute can be on the class, which is
|
|
56
|
+
the string names of attributes in the order they should be
|
|
57
|
+
presented.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
return_fileobj = None
|
|
61
|
+
if not writer:
|
|
62
|
+
return_fileobj = StringIO()
|
|
63
|
+
writer = csv.writer(return_fileobj)
|
|
64
|
+
elif not hasattr(writer, 'writerow'):
|
|
65
|
+
writer = csv.writer(writer)
|
|
66
|
+
|
|
67
|
+
if isinstance(soClass, sqlobject.SQLObject.SelectResultsClass):
|
|
68
|
+
assert select is None, (
|
|
69
|
+
"You cannot pass in a select argument (%r) "
|
|
70
|
+
"and a SelectResults argument (%r) for soClass" %
|
|
71
|
+
(select, soClass))
|
|
72
|
+
select = soClass
|
|
73
|
+
soClass = select.sourceClass
|
|
74
|
+
elif select is None:
|
|
75
|
+
select = soClass.select()
|
|
76
|
+
if getattr(soClass, 'csvOrderBy', None):
|
|
77
|
+
select = select.orderBy(soClass.csvOrderBy)
|
|
78
|
+
|
|
79
|
+
if orderBy:
|
|
80
|
+
select = select.orderBy(orderBy)
|
|
81
|
+
if connection:
|
|
82
|
+
select = select.connection(connection)
|
|
83
|
+
|
|
84
|
+
_actually_export_csv(soClass, select, writer)
|
|
85
|
+
|
|
86
|
+
if return_fileobj:
|
|
87
|
+
# They didn't pass any writer or file object in, so we return
|
|
88
|
+
# the string result:
|
|
89
|
+
return return_fileobj.getvalue()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _actually_export_csv(soClass, select, writer):
|
|
93
|
+
attributes, titles = _find_columns(soClass)
|
|
94
|
+
writer.writerow(titles)
|
|
95
|
+
for soInstance in select:
|
|
96
|
+
row = [getattr(soInstance, attr)
|
|
97
|
+
for attr in attributes]
|
|
98
|
+
writer.writerow(row)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _find_columns(soClass):
|
|
102
|
+
order = []
|
|
103
|
+
attrs = {}
|
|
104
|
+
for col in soClass.sqlmeta.columnList:
|
|
105
|
+
if getattr(col, 'noCSV', False):
|
|
106
|
+
continue
|
|
107
|
+
order.append(col.name)
|
|
108
|
+
title = col.name
|
|
109
|
+
if hasattr(col, 'csvTitle'):
|
|
110
|
+
title = col.csvTitle
|
|
111
|
+
elif getattr(col, 'title', None) is not None:
|
|
112
|
+
title = col.title
|
|
113
|
+
attrs[col.name] = title
|
|
114
|
+
|
|
115
|
+
for attrDesc in getattr(soClass, 'extraCSVColumns', []):
|
|
116
|
+
if isinstance(attrDesc, (list, tuple)):
|
|
117
|
+
attr, title = attrDesc
|
|
118
|
+
else:
|
|
119
|
+
attr = title = attrDesc
|
|
120
|
+
order.append(attr)
|
|
121
|
+
attrs[attr] = title
|
|
122
|
+
|
|
123
|
+
if hasattr(soClass, 'csvColumnOrder'):
|
|
124
|
+
oldOrder = order
|
|
125
|
+
order = soClass.csvColumnOrder
|
|
126
|
+
for attr in order:
|
|
127
|
+
if attr not in oldOrder:
|
|
128
|
+
raise KeyError(
|
|
129
|
+
"Attribute %r in csvColumnOrder (on class %r) "
|
|
130
|
+
"does not exist as a column or in .extraCSVColumns "
|
|
131
|
+
"(I have: %r)" % (attr, soClass, oldOrder))
|
|
132
|
+
oldOrder.remove(attr)
|
|
133
|
+
order.extend(oldOrder)
|
|
134
|
+
|
|
135
|
+
titles = [attrs[attr] for attr in order]
|
|
136
|
+
return order, titles
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def export_csv_zip(soClasses, file=None, zip=None, filename_prefix='',
|
|
140
|
+
connection=None):
|
|
141
|
+
"""
|
|
142
|
+
Export several SQLObject classes into a .zip file. Each
|
|
143
|
+
item in the ``soClasses`` list may be a SQLObject class,
|
|
144
|
+
select result, or ``(soClass, select)`` tuple.
|
|
145
|
+
|
|
146
|
+
Each file in the zip will be named after the class name (with
|
|
147
|
+
``.csv`` appended), or using the filename in the ``.csvFilename``
|
|
148
|
+
attribute.
|
|
149
|
+
|
|
150
|
+
If ``file`` is given, the zip will be written to that. ``file``
|
|
151
|
+
may be a string (a filename) or a file-like object. If not given,
|
|
152
|
+
a string will be returnd.
|
|
153
|
+
|
|
154
|
+
If ``zip`` is given, then the files will be written to that zip
|
|
155
|
+
file.
|
|
156
|
+
|
|
157
|
+
All filenames will be prefixed with ``filename_prefix`` (which may
|
|
158
|
+
be a directory name, for instance).
|
|
159
|
+
"""
|
|
160
|
+
import zipfile
|
|
161
|
+
close_file_when_finished = False
|
|
162
|
+
close_zip_when_finished = True
|
|
163
|
+
return_when_finished = False
|
|
164
|
+
if file:
|
|
165
|
+
if isinstance(file, string_type):
|
|
166
|
+
close_file_when_finished = True
|
|
167
|
+
file = open(file, 'wb')
|
|
168
|
+
elif zip:
|
|
169
|
+
close_zip_when_finished = False
|
|
170
|
+
else:
|
|
171
|
+
return_when_finished = True
|
|
172
|
+
if PY2:
|
|
173
|
+
file = StringIO()
|
|
174
|
+
else:
|
|
175
|
+
# zipfile on python3 requires BytesIO
|
|
176
|
+
file = BytesIO()
|
|
177
|
+
|
|
178
|
+
if not zip:
|
|
179
|
+
zip = zipfile.ZipFile(file, mode='w')
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
_actually_export_classes(soClasses, zip, filename_prefix,
|
|
183
|
+
connection)
|
|
184
|
+
finally:
|
|
185
|
+
if close_zip_when_finished:
|
|
186
|
+
zip.close()
|
|
187
|
+
if close_file_when_finished:
|
|
188
|
+
file.close()
|
|
189
|
+
|
|
190
|
+
if return_when_finished:
|
|
191
|
+
return file.getvalue()
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _actually_export_classes(soClasses, zip, filename_prefix,
|
|
195
|
+
connection):
|
|
196
|
+
for classDesc in soClasses:
|
|
197
|
+
if isinstance(classDesc, (tuple, list)):
|
|
198
|
+
soClass, select = classDesc
|
|
199
|
+
elif isinstance(classDesc, sqlobject.SQLObject.SelectResultsClass):
|
|
200
|
+
select = classDesc
|
|
201
|
+
soClass = select.sourceClass
|
|
202
|
+
else:
|
|
203
|
+
soClass = classDesc
|
|
204
|
+
select = None
|
|
205
|
+
filename = getattr(soClass, 'csvFilename', soClass.__name__)
|
|
206
|
+
if not os.path.splitext(filename)[1]:
|
|
207
|
+
filename += '.csv'
|
|
208
|
+
filename = filename_prefix + filename
|
|
209
|
+
zip.writestr(filename,
|
|
210
|
+
export_csv(soClass, select, connection=connection))
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Import from a CSV file or directory of files.
|
|
3
|
+
|
|
4
|
+
CSV files should have a header line that lists columns. Headers can
|
|
5
|
+
also be appended with ``:type`` to indicate the type of the field.
|
|
6
|
+
``escaped`` is the default, though it can be overridden by the importer.
|
|
7
|
+
Supported types:
|
|
8
|
+
|
|
9
|
+
``:python``:
|
|
10
|
+
A python expression, run through ``eval()``. This can be a
|
|
11
|
+
security risk, pass in ``allow_python=False`` if you don't want to
|
|
12
|
+
allow it.
|
|
13
|
+
|
|
14
|
+
``:int``:
|
|
15
|
+
Integer
|
|
16
|
+
|
|
17
|
+
``:float``:
|
|
18
|
+
Float
|
|
19
|
+
|
|
20
|
+
``:str``:
|
|
21
|
+
String
|
|
22
|
+
|
|
23
|
+
``:escaped``:
|
|
24
|
+
A string with backslash escapes (note that you don't put quotation
|
|
25
|
+
marks around the value)
|
|
26
|
+
|
|
27
|
+
``:base64``:
|
|
28
|
+
A base64-encoded string
|
|
29
|
+
|
|
30
|
+
``:date``:
|
|
31
|
+
ISO date, like YYYY-MM-DD; this can also be ``NOW+days`` or
|
|
32
|
+
``NOW-days``
|
|
33
|
+
|
|
34
|
+
``:datetime``:
|
|
35
|
+
ISO date/time like YYYY-MM-DDTHH:MM:SS (either T or a space can be
|
|
36
|
+
used to separate the time, and seconds are optional). This can
|
|
37
|
+
also be ``NOW+seconds`` or ``NOW-seconds``
|
|
38
|
+
|
|
39
|
+
``:bool``:
|
|
40
|
+
Converts true/false/yes/no/on/off/1/0 to boolean value
|
|
41
|
+
|
|
42
|
+
``:ref``:
|
|
43
|
+
This will be resolved to the ID of the object named in this column
|
|
44
|
+
(None if the column is empty). @@: Since there's no ordering,
|
|
45
|
+
there's no way to promise the object already exists.
|
|
46
|
+
|
|
47
|
+
You can also get back references to the objects if you have a special
|
|
48
|
+
``[name]`` column.
|
|
49
|
+
|
|
50
|
+
Any column named ``[comment]`` or with no name will be ignored.
|
|
51
|
+
|
|
52
|
+
In any column you can put ``[default]`` to exclude the value and use
|
|
53
|
+
whatever default the class wants. ``[null]`` will use NULL.
|
|
54
|
+
|
|
55
|
+
Lines that begin with ``[comment]`` are ignored.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
import csv
|
|
59
|
+
from datetime import datetime, date, timedelta
|
|
60
|
+
import os
|
|
61
|
+
import time
|
|
62
|
+
import types
|
|
63
|
+
|
|
64
|
+
__all__ = ['load_csv_from_directory',
|
|
65
|
+
'load_csv',
|
|
66
|
+
'create_data']
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
DEFAULT_TYPE = 'escaped'
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def create_data(data, class_getter, keyorder=None):
|
|
73
|
+
"""
|
|
74
|
+
Create the ``data``, which is the return value from
|
|
75
|
+
``load_csv()``. Classes will be resolved with the callable
|
|
76
|
+
``class_getter``; or if ``class_getter`` is a module then the
|
|
77
|
+
class names will be attributes of that.
|
|
78
|
+
|
|
79
|
+
Returns a dictionary of ``{object_name: object(s)}``, using the
|
|
80
|
+
names from the ``[name]`` columns (if there are any). If a name
|
|
81
|
+
is used multiple times, you get a list of objects, not a single
|
|
82
|
+
object.
|
|
83
|
+
|
|
84
|
+
If ``keyorder`` is given, then the keys will be retrieved in that
|
|
85
|
+
order. It can be a list/tuple of names, or a sorting function.
|
|
86
|
+
If not given and ``class_getter`` is a module and has a
|
|
87
|
+
``soClasses`` function, then that will be used for the order.
|
|
88
|
+
"""
|
|
89
|
+
objects = {}
|
|
90
|
+
classnames = data.keys()
|
|
91
|
+
if (not keyorder and isinstance(class_getter, types.ModuleType)
|
|
92
|
+
and hasattr(class_getter, 'soClasses')):
|
|
93
|
+
keyorder = [c.__name__ for c in class_getter.soClasses]
|
|
94
|
+
if not keyorder:
|
|
95
|
+
classnames.sort()
|
|
96
|
+
elif isinstance(keyorder, (list, tuple)):
|
|
97
|
+
all = classnames
|
|
98
|
+
classnames = [name for name in keyorder if name in classnames]
|
|
99
|
+
for name in all:
|
|
100
|
+
if name not in classnames:
|
|
101
|
+
classnames.append(name)
|
|
102
|
+
else:
|
|
103
|
+
classnames.sort(keyorder)
|
|
104
|
+
for classname in classnames:
|
|
105
|
+
items = data[classname]
|
|
106
|
+
if not items:
|
|
107
|
+
continue
|
|
108
|
+
if isinstance(class_getter, types.ModuleType):
|
|
109
|
+
soClass = getattr(class_getter, classname)
|
|
110
|
+
else:
|
|
111
|
+
soClass = class_getter(classname)
|
|
112
|
+
for item in items:
|
|
113
|
+
for key, value in item.items():
|
|
114
|
+
if isinstance(value, Reference):
|
|
115
|
+
resolved = objects.get(value.name)
|
|
116
|
+
if not resolved:
|
|
117
|
+
raise ValueError(
|
|
118
|
+
"Object reference to %r does not have target"
|
|
119
|
+
% value.name)
|
|
120
|
+
elif (isinstance(resolved, list) and len(resolved) > 1):
|
|
121
|
+
raise ValueError(
|
|
122
|
+
"Object reference to %r is ambiguous (got %r)"
|
|
123
|
+
% (value.name, resolved))
|
|
124
|
+
item[key] = resolved.id
|
|
125
|
+
if '[name]' in item:
|
|
126
|
+
name = item.pop('[name]').strip()
|
|
127
|
+
else:
|
|
128
|
+
name = None
|
|
129
|
+
inst = soClass(**item)
|
|
130
|
+
if name:
|
|
131
|
+
if name in objects:
|
|
132
|
+
if isinstance(objects[name], list):
|
|
133
|
+
objects[name].append(inst)
|
|
134
|
+
else:
|
|
135
|
+
objects[name] = [objects[name], inst]
|
|
136
|
+
else:
|
|
137
|
+
objects[name] = inst
|
|
138
|
+
return objects
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def load_csv_from_directory(directory,
|
|
142
|
+
allow_python=True, default_type=DEFAULT_TYPE,
|
|
143
|
+
allow_multiple_classes=True):
|
|
144
|
+
"""
|
|
145
|
+
Load the data from all the files in a directory. Filenames
|
|
146
|
+
indicate the class, with ``general.csv`` for data not associated
|
|
147
|
+
with a class. Return data just like ``load_csv`` does.
|
|
148
|
+
|
|
149
|
+
This might cause problems on case-insensitive filesystems.
|
|
150
|
+
"""
|
|
151
|
+
results = {}
|
|
152
|
+
for filename in os.listdir(directory):
|
|
153
|
+
base, ext = os.path.splitext(filename)
|
|
154
|
+
if ext.lower() != '.csv':
|
|
155
|
+
continue
|
|
156
|
+
f = open(os.path.join(directory, filename), 'rb')
|
|
157
|
+
csvreader = csv.reader(f)
|
|
158
|
+
data = load_csv(csvreader, allow_python=allow_python,
|
|
159
|
+
default_type=default_type,
|
|
160
|
+
default_class=base,
|
|
161
|
+
allow_multiple_classes=allow_multiple_classes)
|
|
162
|
+
f.close()
|
|
163
|
+
for classname, items in data.items():
|
|
164
|
+
results.setdefault(classname, []).extend(items)
|
|
165
|
+
return results
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def load_csv(csvreader, allow_python=True, default_type=DEFAULT_TYPE,
|
|
169
|
+
default_class=None, allow_multiple_classes=True):
|
|
170
|
+
"""
|
|
171
|
+
Loads the CSV file, returning a list of dictionaries with types
|
|
172
|
+
coerced.
|
|
173
|
+
"""
|
|
174
|
+
current_class = default_class
|
|
175
|
+
current_headers = None
|
|
176
|
+
results = {}
|
|
177
|
+
|
|
178
|
+
for row in csvreader:
|
|
179
|
+
if not [cell for cell in row if cell.strip()]:
|
|
180
|
+
# empty row
|
|
181
|
+
continue
|
|
182
|
+
|
|
183
|
+
if row and row[0].strip() == 'CLASS:':
|
|
184
|
+
if not allow_multiple_classes:
|
|
185
|
+
raise ValueError(
|
|
186
|
+
"CLASS: line in CSV file, but multiple classes "
|
|
187
|
+
"are not allowed in this file (line: %r)" % row)
|
|
188
|
+
if not row[1:]:
|
|
189
|
+
raise ValueError(
|
|
190
|
+
"CLASS: in line in CSV file, with no class name "
|
|
191
|
+
"in next column (line: %r)" % row)
|
|
192
|
+
current_class = row[1]
|
|
193
|
+
current_headers = None
|
|
194
|
+
continue
|
|
195
|
+
|
|
196
|
+
if not current_class:
|
|
197
|
+
raise ValueError(
|
|
198
|
+
"No CLASS: line given, and there is no default class "
|
|
199
|
+
"for this file (line: %r)" % row)
|
|
200
|
+
|
|
201
|
+
if current_headers is None:
|
|
202
|
+
current_headers = _parse_headers(row, default_type,
|
|
203
|
+
allow_python=allow_python)
|
|
204
|
+
continue
|
|
205
|
+
|
|
206
|
+
if row[0] == '[comment]':
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
# Pad row with empty strings:
|
|
210
|
+
row += [''] * (len(current_headers) - len(row))
|
|
211
|
+
row_converted = {}
|
|
212
|
+
for value, (name, coercer, args) in zip(row, current_headers):
|
|
213
|
+
if name is None:
|
|
214
|
+
# Comment
|
|
215
|
+
continue
|
|
216
|
+
if value == '[default]':
|
|
217
|
+
continue
|
|
218
|
+
if value == '[null]':
|
|
219
|
+
row_converted[name] = None
|
|
220
|
+
continue
|
|
221
|
+
args = (value,) + args
|
|
222
|
+
row_converted[name] = coercer(*args)
|
|
223
|
+
|
|
224
|
+
results.setdefault(current_class, []).append(row_converted)
|
|
225
|
+
|
|
226
|
+
return results
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _parse_headers(header_row, default_type, allow_python=True):
|
|
230
|
+
headers = []
|
|
231
|
+
for name in header_row:
|
|
232
|
+
original_name = name
|
|
233
|
+
if ':' in name:
|
|
234
|
+
name, type = name.split(':', 1)
|
|
235
|
+
else:
|
|
236
|
+
type = default_type
|
|
237
|
+
if type == 'python' and not allow_python:
|
|
238
|
+
raise ValueError(
|
|
239
|
+
":python header given when python headers are not allowed "
|
|
240
|
+
"(with header %r)" % original_name)
|
|
241
|
+
name = name.strip()
|
|
242
|
+
if name == '[comment]' or not name:
|
|
243
|
+
headers.append((None, None, None))
|
|
244
|
+
continue
|
|
245
|
+
type = type.strip().lower()
|
|
246
|
+
if '(' in type:
|
|
247
|
+
type, arg = type.split('(', 1)
|
|
248
|
+
if not arg.endswith(')'):
|
|
249
|
+
raise ValueError(
|
|
250
|
+
"Arguments (in ()'s) do not end with ): %r"
|
|
251
|
+
% original_name)
|
|
252
|
+
args = (arg[:-1],)
|
|
253
|
+
else:
|
|
254
|
+
args = ()
|
|
255
|
+
if name == '[name]':
|
|
256
|
+
type = 'str'
|
|
257
|
+
coercer, args = get_coercer(type)
|
|
258
|
+
headers.append((name, coercer, args))
|
|
259
|
+
return headers
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
_coercers = {}
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def get_coercer(type):
|
|
266
|
+
if type not in _coercers:
|
|
267
|
+
raise ValueError(
|
|
268
|
+
"Coercion type %r not known (I know: %s)"
|
|
269
|
+
% (type, ', '.join(_coercers.keys())))
|
|
270
|
+
return _coercers[type]
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def register_coercer(type, coercer, *args):
|
|
274
|
+
_coercers[type] = (coercer, args)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def identity(v):
|
|
278
|
+
return v
|
|
279
|
+
|
|
280
|
+
register_coercer('str', identity)
|
|
281
|
+
register_coercer('string', identity)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def decode_string(v, encoding):
|
|
285
|
+
return v.decode(encoding)
|
|
286
|
+
|
|
287
|
+
register_coercer('escaped', decode_string, 'string_escape')
|
|
288
|
+
register_coercer('strescaped', decode_string, 'string_escape')
|
|
289
|
+
register_coercer('base64', decode_string, 'base64')
|
|
290
|
+
|
|
291
|
+
register_coercer('int', int)
|
|
292
|
+
register_coercer('float', float)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def parse_python(v):
|
|
296
|
+
return eval(v, {}, {})
|
|
297
|
+
|
|
298
|
+
register_coercer('python', parse_python)
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def parse_date(v):
|
|
302
|
+
v = v.strip()
|
|
303
|
+
if not v:
|
|
304
|
+
return None
|
|
305
|
+
if v.startswith('NOW-') or v.startswith('NOW+'):
|
|
306
|
+
days = int(v[3:])
|
|
307
|
+
now = date.today()
|
|
308
|
+
return now + timedelta(days)
|
|
309
|
+
else:
|
|
310
|
+
parsed = time.strptime(v, '%Y-%m-%d')
|
|
311
|
+
return date.fromtimestamp(time.mktime(parsed))
|
|
312
|
+
|
|
313
|
+
register_coercer('date', parse_date)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def parse_datetime(v):
|
|
317
|
+
v = v.strip()
|
|
318
|
+
if not v:
|
|
319
|
+
return None
|
|
320
|
+
if v.startswith('NOW-') or v.startswith('NOW+'):
|
|
321
|
+
seconds = int(v[3:])
|
|
322
|
+
now = datetime.now()
|
|
323
|
+
return now + timedelta(0, seconds)
|
|
324
|
+
else:
|
|
325
|
+
fmts = ['%Y-%m-%dT%H:%M:%S',
|
|
326
|
+
'%Y-%m-%d %H:%M:%S',
|
|
327
|
+
'%Y-%m-%dT%H:%M',
|
|
328
|
+
'%Y-%m-%d %H:%M']
|
|
329
|
+
for fmt in fmts[:-1]:
|
|
330
|
+
try:
|
|
331
|
+
parsed = time.strptime(v, fmt)
|
|
332
|
+
break
|
|
333
|
+
except ValueError:
|
|
334
|
+
pass
|
|
335
|
+
else:
|
|
336
|
+
parsed = time.strptime(v, fmts[-1])
|
|
337
|
+
return datetime.fromtimestamp(time.mktime(parsed))
|
|
338
|
+
|
|
339
|
+
register_coercer('datetime', parse_datetime)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
class Reference(object):
|
|
343
|
+
def __init__(self, name):
|
|
344
|
+
self.name = name
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def parse_ref(v):
|
|
348
|
+
if not v.strip():
|
|
349
|
+
return None
|
|
350
|
+
else:
|
|
351
|
+
return Reference(v)
|
|
352
|
+
|
|
353
|
+
register_coercer('ref', parse_ref)
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def parse_bool(v):
|
|
357
|
+
v = v.strip().lower()
|
|
358
|
+
if v in ('y', 'yes', 't', 'true', 'on', '1'):
|
|
359
|
+
return True
|
|
360
|
+
elif v in ('n', 'no', 'f', 'false', 'off', '0'):
|
|
361
|
+
return False
|
|
362
|
+
raise ValueError(
|
|
363
|
+
"Value is not boolean-like: %r" % v)
|
|
364
|
+
|
|
365
|
+
register_coercer('bool', parse_bool)
|
|
366
|
+
register_coercer('boolean', parse_bool)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from sqlobject.compat import load_module_from_file
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def load_module(module_name):
|
|
7
|
+
mod = __import__(module_name)
|
|
8
|
+
components = module_name.split('.')
|
|
9
|
+
for comp in components[1:]:
|
|
10
|
+
mod = getattr(mod, comp)
|
|
11
|
+
return mod
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def load_module_from_name(filename, module_name):
|
|
15
|
+
if module_name in sys.modules:
|
|
16
|
+
return sys.modules[module_name]
|
|
17
|
+
init_filename = os.path.join(os.path.dirname(filename), '__init__.py')
|
|
18
|
+
if not os.path.exists(init_filename):
|
|
19
|
+
try:
|
|
20
|
+
f = open(init_filename, 'w')
|
|
21
|
+
except (OSError, IOError) as e:
|
|
22
|
+
raise IOError(
|
|
23
|
+
'Cannot write __init__.py file into directory %s (%s)\n'
|
|
24
|
+
% (os.path.dirname(filename), e))
|
|
25
|
+
f.write('#\n')
|
|
26
|
+
f.close()
|
|
27
|
+
if module_name in sys.modules:
|
|
28
|
+
return sys.modules[module_name]
|
|
29
|
+
if '.' in module_name:
|
|
30
|
+
parent_name = '.'.join(module_name.split('.')[:-1])
|
|
31
|
+
base_name = module_name.split('.')[-1]
|
|
32
|
+
load_module_from_name(os.path.dirname(filename), parent_name)
|
|
33
|
+
else:
|
|
34
|
+
base_name = module_name
|
|
35
|
+
return load_module_from_file(base_name, module_name, filename)
|