| commit 272: | c6b41202ac26 |
| parent 271: | fc1a25c06220 |
| branch: | default |
2 months ago
Changed (Δ10.8 KB):
shabti/templates/auth/+package+/tests/__init__.py_tmpl (9 lines added, 12 lines removed)
shabti/templates/auth_repozepylons/+package+/tests/__init__.py_tmpl (9 lines added, 12 lines removed)
shabti/templates/auth_repozewhat/+package+/tests/__init__.py_tmpl (9 lines added, 12 lines removed)
shabti/templates/auth_repozewho/+package+/tests/__init__.py_tmpl (9 lines added, 12 lines removed)
shabti/templates/authkit/+package+/tests/__init__.py_tmpl (9 lines added, 12 lines removed)
shabti/templates/authkit/+package+/tests/test_models.py (31 lines added, 0 lines removed)
shabti/templates/default/+package+/lib/base.py_tmpl (3 lines added, 2 lines removed)
shabti/templates/default/+package+/lib/fixtures.py_tmpl (134 lines added, 23 lines removed)
shabti/templates/default/+package+/tests/__init__.py_tmpl (17 lines added, 14 lines removed)
shabti/templates/default/+package+/tests/test_models.py_tmpl (15 lines added, 8 lines removed)
shabti/templates/microsite/+package+/lib/fixtures.py_tmpl (134 lines added, 23 lines removed)
shabti/templates/microsite/+package+/tests/__init__.py_tmpl (16 lines added, 0 lines removed)
shabti/templates/microsite/+package+/tests/test_models.py_tmpl (31 lines added, 0 lines removed)
Up to file-list shabti/templates/auth/+package+/tests/__init__.py_tmpl:
| … | … | @@ -49,19 +49,16 @@ Session = elixir.session = meta.Session |
49 |
49 |
|
50 |
50 |
class Individual(Entity): |
51 |
51 |
"""Table 'Individual'. |
52 |
||
53 |
>>> me = Individual('Groucho') |
|
54 |
||
55 |
# 'name' field is converted to lowercase |
|
56 |
>>> me.name |
|
57 |
'groucho' |
|
52 |
||
53 |
used in test_models.py |
|
54 |
||
55 |
>>> me = Individual() |
|
56 |
||
58 |
57 |
""" |
59 |
name = Field(String(20), unique=True) |
|
60 |
favorite_color = Field(String(20)) |
|
61 |
||
62 |
def __init__(self, name, favorite_color=None): |
|
63 |
self.name = str(name).lower() |
|
64 |
self.favorite_color = favorite_color |
|
58 |
name = Field(Unicode(20), unique=True) |
|
59 |
favorite_color = Field(Unicode(20)) |
|
60 |
created = Field(DateTime) |
|
61 |
active = Field(Boolean) |
|
65 |
62 |
|
66 |
63 |
setup_all() |
67 |
64 |
Up to file-list shabti/templates/auth_repozepylons/+package+/tests/__init__.py_tmpl:
| … | … | @@ -50,19 +50,16 @@ Session = elixir.session = meta.Session |
50 |
50 |
|
51 |
51 |
class Individual(Entity): |
52 |
52 |
"""Table 'Individual'. |
53 |
||
54 |
>>> me = Individual('Groucho') |
|
55 |
||
56 |
# 'name' field is converted to lowercase |
|
57 |
>>> me.name |
|
58 |
'groucho' |
|
53 |
||
54 |
used in test_models.py |
|
55 |
||
56 |
>>> me = Individual() |
|
57 |
||
59 |
58 |
""" |
60 |
name = Field(String(20), unique=True) |
|
61 |
favorite_color = Field(String(20)) |
|
62 |
||
63 |
def __init__(self, name, favorite_color=None): |
|
64 |
self.name = str(name).lower() |
|
65 |
self.favorite_color = favorite_color |
|
59 |
name = Field(Unicode(20), unique=True) |
|
60 |
favorite_color = Field(Unicode(20)) |
|
61 |
created = Field(DateTime) |
|
62 |
active = Field(Boolean) |
|
66 |
63 |
|
67 |
64 |
setup_all() |
68 |
65 |
Up to file-list shabti/templates/auth_repozewhat/+package+/tests/__init__.py_tmpl:
| … | … | @@ -49,19 +49,16 @@ Session = elixir.session = meta.Session |
49 |
49 |
|
50 |
50 |
class Individual(Entity): |
51 |
51 |
"""Table 'Individual'. |
52 |
||
53 |
>>> me = Individual('Groucho') |
|
54 |
||
55 |
# 'name' field is converted to lowercase |
|
56 |
>>> me.name |
|
57 |
'groucho' |
|
52 |
||
53 |
used in test_models.py |
|
54 |
||
55 |
>>> me = Individual() |
|
56 |
||
58 |
57 |
""" |
59 |
name = Field(String(20), unique=True) |
|
60 |
favorite_color = Field(String(20)) |
|
61 |
||
62 |
def __init__(self, name, favorite_color=None): |
|
63 |
self.name = str(name).lower() |
|
64 |
self.favorite_color = favorite_color |
|
58 |
name = Field(Unicode(20), unique=True) |
|
59 |
favorite_color = Field(Unicode(20)) |
|
60 |
created = Field(DateTime) |
|
61 |
active = Field(Boolean) |
|
65 |
62 |
|
66 |
63 |
setup_all() |
67 |
64 |
Up to file-list shabti/templates/auth_repozewho/+package+/tests/__init__.py_tmpl:
| … | … | @@ -49,19 +49,16 @@ Session = elixir.session = meta.Session |
49 |
49 |
|
50 |
50 |
class Individual(Entity): |
51 |
51 |
"""Table 'Individual'. |
52 |
||
53 |
>>> me = Individual('Groucho') |
|
54 |
||
55 |
# 'name' field is converted to lowercase |
|
56 |
>>> me.name |
|
57 |
'groucho' |
|
52 |
||
53 |
used in test_models.py |
|
54 |
||
55 |
>>> me = Individual() |
|
56 |
||
58 |
57 |
""" |
59 |
name = Field(String(20), unique=True) |
|
60 |
favorite_color = Field(String(20)) |
|
61 |
||
62 |
def __init__(self, name, favorite_color=None): |
|
63 |
self.name = str(name).lower() |
|
64 |
self.favorite_color = favorite_color |
|
58 |
name = Field(Unicode(20), unique=True) |
|
59 |
favorite_color = Field(Unicode(20)) |
|
60 |
created = Field(DateTime) |
|
61 |
active = Field(Boolean) |
|
65 |
62 |
|
66 |
63 |
setup_all() |
67 |
64 |
Up to file-list shabti/templates/authkit/+package+/tests/__init__.py_tmpl:
| … | … | @@ -49,19 +49,16 @@ Session = elixir.session = meta.Session |
49 |
49 |
|
50 |
50 |
class Individual(Entity): |
51 |
51 |
"""Table 'Individual'. |
52 |
||
53 |
>>> me = Individual('Groucho') |
|
54 |
||
55 |
# 'name' field is converted to lowercase |
|
56 |
>>> me.name |
|
57 |
'groucho' |
|
52 |
||
53 |
used in test_models.py |
|
54 |
||
55 |
>>> me = Individual() |
|
56 |
||
58 |
57 |
""" |
59 |
name = Field(String(20), unique=True) |
|
60 |
favorite_color = Field(String(20)) |
|
61 |
||
62 |
def __init__(self, name, favorite_color=None): |
|
63 |
self.name = str(name).lower() |
|
64 |
self.favorite_color = favorite_color |
|
58 |
name = Field(Unicode(20), unique=True) |
|
59 |
favorite_color = Field(Unicode(20)) |
|
60 |
created = Field(DateTime) |
|
61 |
active = Field(Boolean) |
|
65 |
62 |
|
66 |
63 |
setup_all() |
67 |
64 |
Up to file-list shabti/templates/authkit/+package+/tests/test_models.py:
1 |
from sqlalchemy.exceptions import IntegrityError |
|
2 |
from {{package}}.tests import * |
|
3 |
from {{package}}.tests import Session, metadata, Individual, create_all, drop_all |
|
4 |
import logging |
|
5 |
log = logging.getLogger(__name__) |
|
6 |
||
7 |
class TestMyModel(TestModel): |
|
8 |
||
9 |
def test_simpleassert(self): |
|
10 |
"""test description |
|
11 |
""" |
|
12 |
einstein = Individual(name = u'einstein') |
|
13 |
||
14 |
Session.commit() |
|
15 |
||
16 |
ind1 = Individual.get_by(name = u'einstein') |
|
17 |
assert ind1 == einstein |
|
18 |
||
19 |
def test_exception(self): |
|
20 |
me = Individual(name = u'giuseppe') |
|
21 |
me_again = Individual(name = u'giuseppe') |
|
22 |
self.assertRaises(IntegrityError, Session.commit) |
|
23 |
Session.rollback() |
|
24 |
||
25 |
def test_many(self): |
|
26 |
from aplus.lib.fixtures import lorem_ipsum |
|
27 |
for i in xrange(5): |
|
28 |
lorem_ipsum(Individual) |
|
29 |
Session.commit() |
|
30 |
assert len(Session.query(Individual).all()) == 50 |
|
31 |
Up to file-list shabti/templates/default/+package+/lib/base.py_tmpl:
| … | … | @@ -15,11 +15,12 @@ class BaseController(WSGIController): |
15 |
15 |
# WSGIController.__call__ dispatches to the Controller method |
16 |
16 |
# the request is routed to. This routing information is |
17 |
17 |
# available in environ['pylons.routes_dict'] |
18 |
||
18 |
||
19 |
19 |
# Insert any code to be run per request here. |
20 |
||
20 |
||
21 |
21 |
try: |
22 |
22 |
return WSGIController.__call__(self, environ, start_response) |
23 |
23 |
finally: |
24 |
24 |
model.Session.remove() |
25 |
25 |
|
26 |
Up to file-list shabti/templates/default/+package+/lib/fixtures.py_tmpl:
1 |
# -*- coding: utf-8 -*- |
|
1 |
2 |
import types |
2 |
3 |
import sys |
3 |
4 |
import os |
| … | … | @@ -6,18 +7,26 @@ import simplejson |
6 |
7 |
from {{package}} import model as model |
7 |
8 |
|
8 |
9 |
""" |
9 |
This module can be used for loading data into your models, for example when setting up default application data, |
|
10 |
unit tests, JSON export/import and importing/exporting legacy data. Data is serialized to and from the JSON format. |
|
10 |
This module can be used for loading data into your models, for example when |
|
11 |
setting up default application data, unit tests, JSON export/import and |
|
12 |
importing/exporting legacy data. Data is serialized to and from the JSON |
|
13 |
format. |
|
11 |
14 |
""" |
12 |
15 |
|
13 |
16 |
VALID_FIXTURE_FILE_EXTENSIONS = ['.json'] |
14 |
17 |
|
15 |
18 |
def load_data(model, filename=None, base_dir=None): |
16 |
"""Installs provided fixture files into given model. Filename may be directory, file or list of dirs or files. If filename is |
|
17 |
None, assumes that source file is located in fixtures/model_module_name/model_tablename.yaml of your application directory, |
|
18 |
for example MyProject/fixtures/news/newsitems.yaml. The base_dir argument is the top package of the application unless |
|
19 |
specified. You can also pass the name of a table instead of a model class.""" |
|
20 |
||
19 |
"""\ |
|
20 |
Installs provided fixture files into given model. Filename may be a |
|
21 |
directory, file or list of dirs or files. |
|
22 |
If ``filename`` is ``None``, assumes that source file is located in |
|
23 |
:file:`fixtures/model_module_name/model_tablename.yaml` of the |
|
24 |
application directory, e.g. :file:`MyProject/fixtures/news/newsitems.yaml`. |
|
25 |
The base_dir argument is the top package of the application unless |
|
26 |
specified. You can also pass the name of a table instead of a model |
|
27 |
class. |
|
28 |
""" |
|
29 |
||
21 |
30 |
if type(model) is types.StringType: |
22 |
31 |
return load_data_to_table(model, filename, base_dir) |
23 |
32 |
else: |
| … | … | @@ -26,17 +35,22 @@ def load_data(model, filename=None, base |
26 |
35 |
return _load_data_from_file(model, filename) |
27 |
36 |
|
28 |
37 |
def load_data_to_table(table, filename=None, base_dir=None): |
29 |
""" |
|
38 |
"""\ |
|
39 |
Installs data directly into a table. Useful if table does not have a |
|
40 |
corresponding model, for example a many-to-many join table. |
|
30 |
41 |
""" |
31 |
42 |
|
32 |
43 |
if filename is None: |
33 |
44 |
filename = _default_fixture_path_for_table(table, base_dir) |
34 |
45 |
_load_data_to_table(table, filename) |
35 |
||
46 |
||
36 |
47 |
def dump_data(model, filename=None, **params): |
37 |
"""Dumps data to given destination. Params are optional arguments for selecting data. If filename is None, assumes that destination |
|
38 |
file is located in fixtures/model_module_name/model_name_lowercase.yaml of your application directory, for example |
|
39 |
|
|
48 |
"""\ |
|
49 |
Dumps data to given destination. Params are optional arguments for |
|
50 |
selecting data. |
|
51 |
If ``filename`` is ``None``, assumes that destination file is located in |
|
52 |
:file:`fixtures/model_module_name/model_name_lowercase.yaml` of the |
|
53 |
application directory, e.g. :file:`MyProject/fixtures/news/newsitem.yaml.` |
|
40 |
54 |
""" |
41 |
55 |
|
42 |
56 |
if filename is None: |
| … | … | @@ -52,7 +66,7 @@ def _default_fixture_path_for_model(mode |
52 |
66 |
module_dirs = model.__module__.split('.', 2)[-1].split('.') |
53 |
67 |
for dir in module_dirs: |
54 |
68 |
path = os.path.join(path, dir) |
55 |
return os.path.join(path, model.table.name + '.json') |
|
69 |
return os.path.join(path, model.table.name + '.json') |
|
56 |
70 |
|
57 |
71 |
def _default_fixture_path_for_table(table, base_dir=None): |
58 |
72 |
if base_dir is None: |
| … | … | @@ -66,14 +80,14 @@ def _default_fixture_path_for_table(tabl |
66 |
80 |
def _is_fixture_file(filename): |
67 |
81 |
basename, ext = os.path.splitext(filename) |
68 |
82 |
return (ext.lower() in VALID_FIXTURE_FILE_EXTENSIONS) |
69 |
||
83 |
||
70 |
84 |
def _load_data_from_dir(model, dirname): |
71 |
85 |
for dirpath, dirnames, filenames in os.walk(dirname): |
72 |
86 |
for filename in filenames: |
73 |
87 |
_load_data_from_file(model, filename) |
74 |
||
88 |
||
75 |
89 |
def _load_data_from_file(model, filename): |
76 |
if not _is_fixture_file(filename): |
|
90 |
if not _is_fixture_file(filename): |
|
77 |
91 |
return |
78 |
92 |
fp = file(filename, 'r') |
79 |
93 |
data = simplejson.load(fp) |
| … | … | @@ -86,11 +100,11 @@ def _load_data_from_file(model, filename |
86 |
100 |
elif type(data) is types.DictType: |
87 |
101 |
retval = {} |
88 |
102 |
for key, item in data.iteritems(): |
89 |
retval[key] = _load_instance_from_dict(model, item) |
|
103 |
retval[key] = _load_instance_from_dict(model, item) |
|
90 |
104 |
return retval |
91 |
105 |
|
92 |
106 |
def _load_data_to_table(tablename, filename): |
93 |
if not _is_fixture_file(filename): |
|
107 |
if not _is_fixture_file(filename): |
|
94 |
108 |
return |
95 |
109 |
fp = file(filename, 'r') |
96 |
110 |
data = simplejson.load(fp) |
| … | … | @@ -104,7 +118,7 @@ def _load_data_to_table(tablename, filen |
104 |
118 |
for key, item in data.iteritems(): |
105 |
119 |
table.insert(item).execute() |
106 |
120 |
return data |
107 |
||
121 |
||
108 |
122 |
def _dump_data_to_file(model, filename, **params): |
109 |
123 |
if params: |
110 |
124 |
queryset = model.select_by(**params) |
| … | … | @@ -116,9 +130,9 @@ def _dump_data_to_file(model, filename, |
116 |
130 |
fp = file(filename, 'w') |
117 |
131 |
simplejson.dump(data, fp) |
118 |
132 |
fp.close() |
119 |
||
133 |
||
120 |
134 |
def _load_instance_from_dict(model, dict): |
121 |
if not dict: return |
|
135 |
if not dict: return |
|
122 |
136 |
instance = model() |
123 |
137 |
fields = model._descriptor.columns.keys() |
124 |
138 |
for k, v in dict.iteritems(): |
| … | … | @@ -135,5 +149,102 @@ def _dump_instance_to_dict(instance): |
135 |
149 |
for field in fields: |
136 |
150 |
d[field] = getattr(instance, field) |
137 |
151 |
return d |
138 |
||
139 |
__all__ = ['load_data', 'dump_data'] |
|
152 |
||
153 |
def lorem_ipsum(entity, n=10): |
|
154 |
from random import randint, uniform, sample, choice |
|
155 |
from datetime import datetime |
|
156 |
# constants. TODO: configurable outside? |
|
157 |
_min_year = 1970 |
|
158 |
_max_year = 2020 |
|
159 |
_min_int = 0 |
|
160 |
_max_int = 999999999 |
|
161 |
_min_float = 0.0 |
|
162 |
_max_float = 999999999.0 |
|
163 |
_max_text = 300 |
|
164 |
_max_paragraphs = 5 |
|
165 |
# cache of collections of foreign key values |
|
166 |
choices = {} |
|
167 |
try: |
|
168 |
# standard generator |
|
169 |
import lipsum |
|
170 |
def _text(n): |
|
171 |
return lipsum.Generator().generate_sentence()[:n] \ |
|
172 |
if n else \ |
|
173 |
lipsum.MarkupGenerator().generate_paragraphs_plain( |
|
174 |
randint(1, _max_paragraphs)) |
|
175 |
||
176 |
except: |
|
177 |
# poorman generator |
|
178 |
def _text(n): |
|
179 |
n = n or _max_text |
|
180 |
v = u'' |
|
181 |
for i in xrange(randint(n/3, n)): |
|
182 |
v += choice( |
|
183 |
u' abcdefh gijk lmn opqr stuv wx yzABCD EFG HIJ KLMNO' + \ |
|
184 |
u' PQRST UVW XYZа бвг деё жзийк лм нопрс туфхц чшщъыь' + \ |
|
185 |
u' эюяАВБ ГДЕ ЁЖЗ ИЙКЛМНОП РСТ У ФХЦЧ ШЩЪ ЫЬЭ ЮЯ ' + \ |
|
186 |
u'1234 567 890') |
|
187 |
return v |
|
188 |
||
189 |
def lorem(fields): |
|
190 |
instance = entity() |
|
191 |
for f in fields: |
|
192 |
# don't touch primary keys. |
|
193 |
# TODO: can there be many primary keys? autoincrement instead? |
|
194 |
if f.primary_key: |
|
195 |
continue |
|
196 |
v = 0 # reasonable default value for fields |
|
197 |
# analyse field type |
|
198 |
t = f.type.__class__.__name__ |
|
199 |
# date |
|
200 |
if t in ['Date', 'DateTime']: |
|
201 |
m = randint(1, 12) |
|
202 |
d = 31 if m in [1, 3, 5, 7, 8, 10, 12] else 30 |
|
203 |
if m == 2: d = 28 # TODO: leaps? |
|
204 |
v = datetime(randint(_min_year, _max_year), m, |
|
205 |
randint(1, d), randint(0, 23), |
|
206 |
randint(0, 59), randint(0, 59)) |
|
207 |
# boolean |
|
208 |
elif t == 'Boolean': |
|
209 |
v = randint(0, 1) |
|
210 |
# integer |
|
211 |
elif t == 'Integer': |
|
212 |
# foreign key -> grab possible choices from related table |
|
213 |
if len(f.foreign_keys) > 0: |
|
214 |
table = f.foreign_keys[0].column.table.name |
|
215 |
v = sample(choices[table], 1)[0] |
|
216 |
# vanilla integer |
|
217 |
else: |
|
218 |
v = randint(_min_int, _max_int) |
|
219 |
# float. TODO: currency subclass? |
|
220 |
elif t == 'Float': |
|
221 |
v = uniform(_min_float, _max_float) |
|
222 |
# text. TODO: Char? |
|
223 |
elif t in ['Unicode', 'UnicodeText']: |
|
224 |
n = f.type.length# or _max_text |
|
225 |
v = _text(n) |
|
226 |
#import pdb; pdb.set_trace() |
|
227 |
if not v is None: |
|
228 |
#print 'Setting [%s] to [%s]' % (f.name, v) |
|
229 |
setattr(instance, f.name, v) |
|
230 |
instance.flush() |
|
231 |
return instance |
|
232 |
||
233 |
# fetch affected fields |
|
234 |
fields = entity._descriptor._columns # TODO: specify excludes? but then |
|
235 |
# defaults must be supplied for them! |
|
236 |
# grab possible keys for foreign keys from related tables |
|
237 |
for f in fields: |
|
238 |
if len(f.foreign_keys) > 0: |
|
239 |
table = f.foreign_keys[0].column.table |
|
240 |
#import pdb; pdb.set_trace() |
|
241 |
ids = [] |
|
242 |
for r in model.session.query(table).all(): |
|
243 |
ids.append(r[0]) |
|
244 |
choices[table.name] = ids |
|
245 |
# generate lorem-ipsum n times |
|
246 |
for i in xrange(n): |
|
247 |
lorem(fields) |
|
248 |
||
249 |
__all__ = ['load_data', 'dump_data', 'lorem_ipsum'] |
|
250 |
Up to file-list shabti/templates/default/+package+/tests/__init__.py_tmpl:
| … | … | @@ -48,19 +48,20 @@ Session = elixir.session = meta.Session |
48 |
48 |
|
49 |
49 |
class Individual(Entity): |
50 |
50 |
"""Table 'Individual'. |
51 |
||
52 |
>>> me = Individual('Groucho') |
|
53 |
||
54 |
# 'name' field is converted to lowercase |
|
55 |
>>> me.name |
|
56 |
'groucho' |
|
51 |
||
52 |
used in test_models.py |
|
53 |
||
54 |
>>> me = Individual() |
|
55 |
||
57 |
56 |
""" |
58 |
name = Field(String(20), unique=True) |
|
59 |
favorite_color = Field(String(20)) |
|
60 |
||
61 |
def __init__(self, name, favorite_color=None): |
|
62 |
self.name = str(name).lower() |
|
63 |
self.favorite_color = favorite_color |
|
57 |
name = Field(Unicode(20), unique=True) |
|
58 |
favorite_color = Field(Unicode(20)) |
|
59 |
created = Field(DateTime) |
|
60 |
active = Field(Boolean) |
|
61 |
||
62 |
# def __init__(self, name, favorite_color=None): |
|
63 |
# self.name = str(name).lower() |
|
64 |
# self.favorite_color = favorite_color |
|
64 |
65 |
|
65 |
66 |
setup_all() |
66 |
67 |
|
| … | … | @@ -76,13 +77,15 @@ class TestModel(TestCase): |
76 |
77 |
|
77 |
78 |
def tearDown(self): |
78 |
79 |
drop_all(engine) |
79 |
||
80 |
||
80 |
81 |
|
81 |
82 |
class TestController(TestModel): |
82 |
||
83 |
||
83 |
84 |
def __init__(self, *args, **kwargs): |
84 |
85 |
wsgiapp = pylons.test.pylonsapp |
85 |
86 |
config = wsgiapp.config |
86 |
87 |
self.app = TestApp(wsgiapp) |
87 |
88 |
url._push_object(URLGenerator(config['routes.map'], environ)) |
88 |
89 |
TestCase.__init__(self, *args, **kwargs) |
90 |
||
91 |
Up to file-list shabti/templates/default/+package+/tests/test_models.py_tmpl:
| … | … | @@ -7,16 +7,23 @@ class TestMyModel(TestModel): |
7 |
7 |
def test_simpleassert(self): |
8 |
8 |
"""test description |
9 |
9 |
""" |
10 |
einstein = Individual('einstein') |
|
11 |
||
10 |
einstein = Individual(name = u'einstein') |
|
11 |
||
12 |
12 |
Session.commit() |
13 |
||
14 |
ind1 = Individual.get_by(name = 'einstein') |
|
13 |
||
14 |
ind1 = Individual.get_by(name = u'einstein') |
|
15 |
15 |
assert ind1 == einstein |
16 |
||
16 |
||
17 |
17 |
def test_exception(self): |
18 |
me = Individual('giuseppe') |
|
19 |
me_again = Individual('giuseppe') |
|
18 |
me = Individual(name = u'giuseppe') |
|
19 |
me_again = Individual(name = u'giuseppe') |
|
20 |
20 |
self.assertRaises(IntegrityError, Session.commit) |
21 |
21 |
Session.rollback() |
22 |
||
22 |
||
23 |
def test_many(self): |
|
24 |
from aplus.lib.fixtures import lorem_ipsum |
|
25 |
for i in xrange(5): |
|
26 |
lorem_ipsum(Individual) |
|
27 |
Session.commit() |
|
28 |
assert len(Session.query(Individual).all()) == 50 |
|
29 |
Up to file-list shabti/templates/microsite/+package+/lib/fixtures.py_tmpl:
1 |
# -*- coding: utf-8 -*- |
|
1 |
2 |
import types |
2 |
3 |
import sys |
3 |
4 |
import os |
| … | … | @@ -6,18 +7,26 @@ import simplejson |
6 |
7 |
from {{package}} import model as model |
7 |
8 |
|
8 |
9 |
""" |
9 |
This module can be used for loading data into your models, for example when setting up default application data, |
|
10 |
unit tests, JSON export/import and importing/exporting legacy data. Data is serialized to and from the JSON format. |
|
10 |
This module can be used for loading data into your models, for example when |
|
11 |
setting up default application data, unit tests, JSON export/import and |
|
12 |
importing/exporting legacy data. Data is serialized to and from the JSON |
|
13 |
format. |
|
11 |
14 |
""" |
12 |
15 |
|
13 |
16 |
VALID_FIXTURE_FILE_EXTENSIONS = ['.json'] |
14 |
17 |
|
15 |
18 |
def load_data(model, filename=None, base_dir=None): |
16 |
"""Installs provided fixture files into given model. Filename may be directory, file or list of dirs or files. If filename is |
|
17 |
None, assumes that source file is located in fixtures/model_module_name/model_tablename.yaml of your application directory, |
|
18 |
for example MyProject/fixtures/news/newsitems.yaml. The base_dir argument is the top package of the application unless |
|
19 |
specified. You can also pass the name of a table instead of a model class.""" |
|
20 |
||
19 |
"""\ |
|
20 |
Installs provided fixture files into given model. Filename may be a |
|
21 |
directory, file or list of dirs or files. |
|
22 |
If ``filename`` is ``None``, assumes that source file is located in |
|
23 |
:file:`fixtures/model_module_name/model_tablename.yaml` of the |
|
24 |
application directory, e.g. :file:`MyProject/fixtures/news/newsitems.yaml`. |
|
25 |
The base_dir argument is the top package of the application unless |
|
26 |
specified. You can also pass the name of a table instead of a model |
|
27 |
class. |
|
28 |
""" |
|
29 |
||
21 |
30 |
if type(model) is types.StringType: |
22 |
31 |
return load_data_to_table(model, filename, base_dir) |
23 |
32 |
else: |
| … | … | @@ -26,17 +35,22 @@ def load_data(model, filename=None, base |
26 |
35 |
return _load_data_from_file(model, filename) |
27 |
36 |
|
28 |
37 |
def load_data_to_table(table, filename=None, base_dir=None): |
29 |
""" |
|
38 |
"""\ |
|
39 |
Installs data directly into a table. Useful if table does not have a |
|
40 |
corresponding model, for example a many-to-many join table. |
|
30 |
41 |
""" |
31 |
42 |
|
32 |
43 |
if filename is None: |
33 |
44 |
filename = _default_fixture_path_for_table(table, base_dir) |
34 |
45 |
_load_data_to_table(table, filename) |
35 |
||
46 |
||
36 |
47 |
def dump_data(model, filename=None, **params): |
37 |
"""Dumps data to given destination. Params are optional arguments for selecting data. If filename is None, assumes that destination |
|
38 |
file is located in fixtures/model_module_name/model_name_lowercase.yaml of your application directory, for example |
|
39 |
|
|
48 |
"""\ |
|
49 |
Dumps data to given destination. Params are optional arguments for |
|
50 |
selecting data. |
|
51 |
If ``filename`` is ``None``, assumes that destination file is located in |
|
52 |
:file:`fixtures/model_module_name/model_name_lowercase.yaml` of the |
|
53 |
application directory, e.g. :file:`MyProject/fixtures/news/newsitem.yaml.` |
|
40 |
54 |
""" |
41 |
55 |
|
42 |
56 |
if filename is None: |
| … | … | @@ -52,7 +66,7 @@ def _default_fixture_path_for_model(mode |
52 |
66 |
module_dirs = model.__module__.split('.', 2)[-1].split('.') |
53 |
67 |
for dir in module_dirs: |
54 |
68 |
path = os.path.join(path, dir) |
55 |
return os.path.join(path, model.table.name + '.json') |
|
69 |
return os.path.join(path, model.table.name + '.json') |
|
56 |
70 |
|
57 |
71 |
def _default_fixture_path_for_table(table, base_dir=None): |
58 |
72 |
if base_dir is None: |
| … | … | @@ -66,14 +80,14 @@ def _default_fixture_path_for_table(tabl |
66 |
80 |
def _is_fixture_file(filename): |
67 |
81 |
basename, ext = os.path.splitext(filename) |
68 |
82 |
return (ext.lower() in VALID_FIXTURE_FILE_EXTENSIONS) |
69 |
||
83 |
||
70 |
84 |
def _load_data_from_dir(model, dirname): |
71 |
85 |
for dirpath, dirnames, filenames in os.walk(dirname): |
72 |
86 |
for filename in filenames: |
73 |
87 |
_load_data_from_file(model, filename) |
74 |
||
88 |
||
75 |
89 |
def _load_data_from_file(model, filename): |
76 |
if not _is_fixture_file(filename): |
|
90 |
if not _is_fixture_file(filename): |
|
77 |
91 |
return |
78 |
92 |
fp = file(filename, 'r') |
79 |
93 |
data = simplejson.load(fp) |
| … | … | @@ -86,11 +100,11 @@ def _load_data_from_file(model, filename |
86 |
100 |
elif type(data) is types.DictType: |
87 |
101 |
retval = {} |
88 |
102 |
for key, item in data.iteritems(): |
89 |
retval[key] = _load_instance_from_dict(model, item) |
|
103 |
retval[key] = _load_instance_from_dict(model, item) |
|
90 |
104 |
return retval |
91 |
105 |
|
92 |
106 |
def _load_data_to_table(tablename, filename): |
93 |
if not _is_fixture_file(filename): |
|
107 |
if not _is_fixture_file(filename): |
|
94 |
108 |
return |
95 |
109 |
fp = file(filename, 'r') |
96 |
110 |
data = simplejson.load(fp) |
| … | … | @@ -104,7 +118,7 @@ def _load_data_to_table(tablename, filen |
104 |
118 |
for key, item in data.iteritems(): |
105 |
119 |
table.insert(item).execute() |
106 |
120 |
return data |
107 |
||
121 |
||
108 |
122 |
def _dump_data_to_file(model, filename, **params): |
109 |
123 |
if params: |
110 |
124 |
queryset = model.select_by(**params) |
| … | … | @@ -116,9 +130,9 @@ def _dump_data_to_file(model, filename, |
116 |
130 |
fp = file(filename, 'w') |
117 |
131 |
simplejson.dump(data, fp) |
118 |
132 |
fp.close() |
119 |
||
133 |
||
120 |
134 |
def _load_instance_from_dict(model, dict): |
121 |
if not dict: return |
|
135 |
if not dict: return |
|
122 |
136 |
instance = model() |
123 |
137 |
fields = model._descriptor.columns.keys() |
124 |
138 |
for k, v in dict.iteritems(): |
| … | … | @@ -135,5 +149,102 @@ def _dump_instance_to_dict(instance): |
135 |
149 |
for field in fields: |
136 |
150 |
d[field] = getattr(instance, field) |
137 |
151 |
return d |
138 |
||
139 |
__all__ = ['load_data', 'dump_data'] |
|
152 |
||
153 |
def lorem_ipsum(entity, n=10): |
|
154 |
from random import randint, uniform, sample, choice |
|
155 |
from datetime import datetime |
|
156 |
# constants. TODO: configurable outside? |
|
157 |
_min_year = 1970 |
|
158 |
_max_year = 2020 |
|
159 |
_min_int = 0 |
|
160 |
_max_int = 999999999 |
|
161 |
_min_float = 0.0 |
|
162 |
_max_float = 999999999.0 |
|
163 |
_max_text = 300 |
|
164 |
_max_paragraphs = 5 |
|
165 |
# cache of collections of foreign key values |
|
166 |
choices = {} |
|
167 |
try: |
|
168 |
# standard generator |
|
169 |
import lipsum |
|
170 |
def _text(n): |
|
171 |
return lipsum.Generator().generate_sentence()[:n] \ |
|
172 |
if n else \ |
|
173 |
lipsum.MarkupGenerator().generate_paragraphs_plain( |
|
174 |
randint(1, _max_paragraphs)) |
|
175 |
||
176 |
except: |
|
177 |
# poorman generator |
|
178 |
def _text(n): |
|
179 |
n = n or _max_text |
|
180 |
v = u'' |
|
181 |
for i in xrange(randint(n/3, n)): |
|
182 |
v += choice( |
|
183 |
u' abcdefh gijk lmn opqr stuv wx yzABCD EFG HIJ KLMNO' + \ |
|
184 |
u' PQRST UVW XYZа бвг деё жзийк лм нопрс туфхц чшщъыь' + \ |
|
185 |
u' эюяАВБ ГДЕ ЁЖЗ ИЙКЛМНОП РСТ У ФХЦЧ ШЩЪ ЫЬЭ ЮЯ ' + \ |
|
186 |
u'1234 567 890') |
|
187 |
return v |
|
188 |
||
189 |
def lorem(fields): |
|
190 |
instance = entity() |
|
191 |
for f in fields: |
|
192 |
# don't touch primary keys. |
|
193 |
# TODO: can there be many primary keys? autoincrement instead? |
|
194 |
if f.primary_key: |
|
195 |
continue |
|
196 |
v = 0 # reasonable default value for fields |
|
197 |
# analyse field type |
|
198 |
t = f.type.__class__.__name__ |
|
199 |
# date |
|
200 |
if t in ['Date', 'DateTime']: |
|
201 |
m = randint(1, 12) |
|
202 |
d = 31 if m in [1, 3, 5, 7, 8, 10, 12] else 30 |
|
203 |
if m == 2: d = 28 # TODO: leaps? |
|
204 |
v = datetime(randint(_min_year, _max_year), m, |
|
205 |
randint(1, d), randint(0, 23), |
|
206 |
randint(0, 59), randint(0, 59)) |
|
207 |
# boolean |
|
208 |
elif t == 'Boolean': |
|
209 |
v = randint(0, 1) |
|
210 |
# integer |
|
211 |
elif t == 'Integer': |
|
212 |
# foreign key -> grab possible choices from related table |
|
213 |
if len(f.foreign_keys) > 0: |
|
214 |
table = f.foreign_keys[0].column.table.name |
|
215 |
v = sample(choices[table], 1)[0] |
|
216 |
# vanilla integer |
|
217 |
else: |
|
218 |
v = randint(_min_int, _max_int) |
|
219 |
# float. TODO: currency subclass? |
|
220 |
elif t == 'Float': |
|
221 |
v = uniform(_min_float, _max_float) |
|
222 |
# text. TODO: Char? |
|
223 |
elif t in ['Unicode', 'UnicodeText']: |
|
224 |
n = f.type.length# or _max_text |
|
225 |
v = _text(n) |
|
226 |
#import pdb; pdb.set_trace() |
|
227 |
if not v is None: |
|
228 |
#print 'Setting [%s] to [%s]' % (f.name, v) |
|
229 |
setattr(instance, f.name, v) |
|
230 |
instance.flush() |
|
231 |
return instance |
|
232 |
||
233 |
# fetch affected fields |
|
234 |
fields = entity._descriptor._columns # TODO: specify excludes? but then |
|
235 |
# defaults must be supplied for them! |
|
236 |
# grab possible keys for foreign keys from related tables |
|
237 |
for f in fields: |
|
238 |
if len(f.foreign_keys) > 0: |
|
239 |
table = f.foreign_keys[0].column.table |
|
240 |
#import pdb; pdb.set_trace() |
|
241 |
ids = [] |
|
242 |
for r in model.session.query(table).all(): |
|
243 |
ids.append(r[0]) |
|
244 |
choices[table.name] = ids |
|
245 |
# generate lorem-ipsum n times |
|
246 |
for i in xrange(n): |
|
247 |
lorem(fields) |
|
248 |
||
249 |
__all__ = ['load_data', 'dump_data', 'lorem_ipsum'] |
|
250 |
Up to file-list shabti/templates/microsite/+package+/tests/__init__.py_tmpl:
| … | … | @@ -47,6 +47,22 @@ engine = sqlalchemy.engine_from_config(c |
47 |
47 |
metadata = elixir.metadata |
48 |
48 |
Session = elixir.session = meta.Session |
49 |
49 |
|
50 |
class Individual(Entity): |
|
51 |
"""Table 'Individual'. |
|
52 |
||
53 |
used in test_models.py |
|
54 |
||
55 |
>>> me = Individual() |
|
56 |
||
57 |
""" |
|
58 |
name = Field(Unicode(20), unique=True) |
|
59 |
favorite_color = Field(Unicode(20)) |
|
60 |
created = Field(DateTime) |
|
61 |
active = Field(Boolean) |
|
62 |
||
63 |
setup_all() |
|
64 |
||
65 |
||
50 |
66 |
class TestModel(TestCase): |
51 |
67 |
|
52 |
68 |
def setUp(self): |
Up to file-list shabti/templates/microsite/+package+/tests/test_models.py_tmpl:
1 |
from sqlalchemy.exceptions import IntegrityError |
|
2 |
from {{package}}.tests import * |
|
3 |
from {{package}}.tests import Session, metadata, Individual, create_all, drop_all |
|
4 |
import logging |
|
5 |
log = logging.getLogger(__name__) |
|
6 |
||
7 |
class TestMyModel(TestModel): |
|
8 |
||
9 |
def test_simpleassert(self): |
|
10 |
"""test description |
|
11 |
""" |
|
12 |
einstein = Individual(name = u'einstein') |
|
13 |
||
14 |
Session.commit() |
|
15 |
||
16 |
ind1 = Individual.get_by(name = u'einstein') |
|
17 |
assert ind1 == einstein |
|
18 |
||
19 |
def test_exception(self): |
|
20 |
me = Individual(name = u'giuseppe') |
|
21 |
me_again = Individual(name = u'giuseppe') |
|
22 |
self.assertRaises(IntegrityError, Session.commit) |
|
23 |
Session.rollback() |
|
24 |
||
25 |
def test_many(self): |
|
26 |
from aplus.lib.fixtures import lorem_ipsum |
|
27 |
for i in xrange(5): |
|
28 |
lorem_ipsum(Individual) |
|
29 |
Session.commit() |
|
30 |
assert len(Session.query(Individual).all()) == 50 |
|
31 |
