Skip to content

Commit c5fa905

Browse files
author
Cameron Brandon White
committed
Merge pull request #23 from cameronbwhite/master
Added acmapi.field_types module and changed import style
2 parents da58f1e + d55000d commit c5fa905

17 files changed

+482
-99
lines changed

acmapi/__init__.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@
99
__version__ = '0.1.0'
1010

1111
from .models import DB
12-
from .resources import \
13-
API, Root, Events, People, Memberships, Officerships
12+
from .resources import API
13+
from .resources import Root
14+
from .resources import Events
15+
from .resources import People
16+
from .resources import Memberships
17+
from .resources import Officerships
1418
from .authentication import AUTH
1519

1620
def create_app(config_files=None, envvars_files=None, *envvars, **other):

acmapi/field_types.py

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import flask
2+
3+
from flask.ext.restful.fields import Url
4+
from flask.ext.restful.fields import Raw
5+
from flask.ext.restful.fields import MarshallingException
6+
7+
try:
8+
from urlparse import urlparse
9+
from urlparse import urlunparse
10+
from urllib import urlencode
11+
except ImportError:
12+
from urllib.parse import urlparse
13+
from urllib.parse import urlunparse
14+
from urllib.parse import urlencode
15+
16+
class Date(Raw):
17+
""" Renders a Date obj as a string """
18+
19+
def __init__(self, date_format, default=None, attribute=None):
20+
super(Date, self).__init__(default, attribute)
21+
self.date_format = date_format
22+
23+
def format(self, x):
24+
try:
25+
return x.strftime(self.date_format)
26+
except AttributeError:
27+
raise MarshallingException("Must be a valid date")
28+
29+
class DateTime(Raw):
30+
""" Renders a DateTime obj as a string """
31+
32+
def __init__(self, datetime_format, default=None, attribute=None):
33+
super(DateTime, self).__init__(default, attribute)
34+
self.datetime_format = datetime_format
35+
36+
def format(self, x):
37+
try:
38+
return x.strftime(self.datetime_format)
39+
except AttributeError:
40+
raise MarshallingException("Must be a valid date and time")
41+
42+
class UrlUsage(Raw):
43+
""" Renders all the usages for a endpoint """
44+
45+
def __init__(self, endpoint, absolute=False, scheme=None):
46+
self.endpoint = endpoint
47+
self.absolute = absolute
48+
self.scheme = scheme
49+
50+
def output(self, obj, key):
51+
urls = []
52+
for rule in flask.current_app.url_map.iter_rules():
53+
if rule.endpoint == self.endpoint:
54+
o = urlparse(flask.url_for(self.endpoint, _external = self.absolute))
55+
scheme = self.scheme if self.scheme is not None else o.scheme
56+
urls.append(urlunparse((scheme, o.netloc, rule.rule, "", "", "")))
57+
return urls
58+
59+
class UrlWithParams(Url):
60+
""" Will render the a url with specified params
61+
62+
params can be either a list or a dict. If params is a list then
63+
the url will contain a param key contained in the list
64+
and the value will be looked up in the obj by the same name. Using
65+
a dict allow you to specify a different key to look up in the obj.
66+
67+
## Example using a list
68+
69+
```python
70+
fields = {
71+
foo: Integer,
72+
url: UrlWithParams('root', params=['foo'])
73+
}
74+
75+
marshal({foo: 1}, fields)
76+
```
77+
78+
Will produce:
79+
80+
http://example.com/?foo=1
81+
82+
## Example using a dict
83+
84+
85+
```python
86+
fields = {
87+
bar: Integer,
88+
url: UrlWithParams('root', params={'foo': 'bar'})
89+
}
90+
91+
marshal({foo: 1}, fields)
92+
```
93+
94+
Will produce:
95+
96+
http://example.com/?foo=1
97+
"""
98+
def __init__(self, endpoint, params=None, absolute=False, scheme=None):
99+
assert(isinstance(params, (type(None), list, dict)))
100+
super(UrlWithParams, self).__init__(
101+
endpoint, absolute, scheme)
102+
self.params = params
103+
104+
def output(self, key, obj):
105+
url = super(UrlWithParams, self).output(key,obj)
106+
o = urlparse(url)
107+
if self.params is None:
108+
return url
109+
elif isinstance(self.params, list):
110+
return urlunparse((o.scheme, o.netloc, o.path, "",
111+
urlencode({ k:obj[k] for k in self.params}), ""))
112+
elif isinstance(self.params, dict):
113+
return urlunparse((o.scheme, o.netloc, o.path, "",
114+
urlencode({ k:obj[v] for k,v in self.params.items()}), ""))
115+
else:
116+
raise TypeError("params must be of type None, list, or dict")

acmapi/fields.py

+22-50
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,18 @@
1-
import flask
2-
from flask.ext.restful import fields
3-
from flask.ext.restful.fields import \
4-
Integer, String, Boolean, Url, Raw
1+
from flask.ext.restful.fields import Integer
2+
from flask.ext.restful.fields import String
3+
from flask.ext.restful.fields import Boolean
4+
from flask.ext.restful.fields import Url
5+
from flask.ext.restful.fields import Raw
6+
from flask.ext.restful.fields import List
7+
from flask.ext.restful.fields import Nested
58

6-
from . import DATE_FORMAT, DATETIME_FORMAT
9+
from .field_types import UrlUsage
10+
from .field_types import UrlWithParams
11+
from .field_types import DateTime
12+
from .field_types import Date
713

8-
try:
9-
from urlparse import urlparse, urlunparse
10-
except ImportError:
11-
from urllib.parse import urlparse, urlunparse
12-
13-
class MarshallingException(Exception):
14-
pass
15-
16-
class DateField(Raw):
17-
def format(self, x):
18-
try:
19-
return x.strftime(DATE_FORMAT)
20-
except AttributeError:
21-
raise MarshallingException("Must be a valid date")
22-
23-
class DateTimeField(Raw):
24-
def format(self, x):
25-
try:
26-
return x.strftime(DATETIME_FORMAT)
27-
except AttributeError:
28-
raise MarshallingException("Must be a valid date")
29-
30-
class UrlUsage(Raw):
31-
32-
def __init__(self, endpoint, absolute=False, scheme=None):
33-
self.endpoint = endpoint
34-
self.absolute = absolute
35-
self.scheme = scheme
36-
37-
def output(self, obj, key):
38-
urls = []
39-
for rule in flask.current_app.url_map.iter_rules():
40-
if rule.endpoint == self.endpoint:
41-
o = urlparse(flask.url_for(self.endpoint, _external = self.absolute))
42-
scheme = self.scheme if self.scheme is not None else o.scheme
43-
urls.append(urlunparse((scheme, o.netloc, rule.rule, "", "", "")))
44-
return urls
14+
from . import DATE_FORMAT
15+
from . import DATETIME_FORMAT
4516

4617
root_fields = {
4718
'events_url': UrlUsage('events', absolute=True),
@@ -59,9 +30,9 @@ def output(self, obj, key):
5930
'location': String,
6031
'editor_id': Integer,
6132
'editor': Url('people', absolute=True),
62-
'edited_at': DateTimeField(attribute='edited_datetime'),
63-
'start': DateTimeField,
64-
'end': DateTimeField,
33+
'edited_at': DateTime(DATETIME_FORMAT, attribute='edited_datetime'),
34+
'start': DateTime(DATETIME_FORMAT),
35+
'end': DateTime(DATETIME_FORMAT),
6536
'canceled': Boolean,
6637
'hidden': Boolean,
6738
'revision': Integer(attribute='index'),
@@ -73,7 +44,7 @@ def output(self, obj, key):
7344
'description': String,
7445
'editor_id': Integer,
7546
'editor': Url('people', absolute=True),
76-
'edited_at': DateTimeField(attribute='edited_datetime'),
47+
'edited_at': DateTime(DATETIME_FORMAT, attribute='edited_datetime'),
7748
'hidden': Boolean,
7849
'revision': Integer(attribute='index'),
7950
'content': String,
@@ -92,21 +63,22 @@ def output(self, obj, key):
9263

9364
membership_fields = {
9465
'id': Integer,
95-
'start_date': DateField,
96-
'end_date': DateField,
66+
'start_date': Date(DATE_FORMAT),
67+
'end_date': Date(DATE_FORMAT),
9768
'person_id': Integer,
9869
'person': Url('people', absolute=True),
9970
}
10071

10172
officership_fields = {
10273
'id': Integer,
10374
'title': String,
104-
'start_date': DateField,
105-
'end_date': DateField,
75+
'start_date': Date(DATE_FORMAT),
76+
'end_date': Date(DATE_FORMAT),
10677
'person_id': Integer,
10778
'person': Url('people', absolute=True),
10879
}
10980

81+
11082
database_fields = {
11183
'dilect': String,
11284
'host': String,

acmapi/resources.py

+15-8
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import flask
22
from flask import Flask, current_app
33
from flask.ext import restful
4-
from flask.ext.restful import \
5-
reqparse, fields, marshal_with, marshal, abort
4+
from flask.ext.restful import reqparse
5+
from flask.ext.restful import fields
6+
from flask.ext.restful import marshal_with
7+
from flask.ext.restful import marshal
8+
from flask.ext.restful import abort
69

710
import sqlalchemy
811
from sqlalchemy.exc import IntegrityError
@@ -17,12 +20,16 @@
1720

1821
from . import models
1922
from .models import DB
20-
from .fields import \
21-
DateField, MarshallingException, \
22-
root_fields, event_fields, post_fields, person_fields, \
23-
membership_fields, officership_fields, database_fields
24-
from .types import \
25-
datetime_type, date_type
23+
from .fields import Date
24+
from .fields import root_fields
25+
from .fields import event_fields
26+
from .fields import post_fields
27+
from .fields import person_fields
28+
from .fields import membership_fields
29+
from .fields import officership_fields
30+
from .fields import database_fields
31+
from .types import datetime_type
32+
from .types import date_type
2633
from .authentication import AUTH
2734
from .argument import CustomArgument
2835

acmapi/types.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
import datetime
88

9-
from . import DATE_FORMAT, DATETIME_FORMAT
9+
from . import DATE_FORMAT
10+
from . import DATETIME_FORMAT
1011

1112
class ValidationError(Exception):
1213
pass

tests/test_database.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44

55
from flask import Flask
66
from acmapi import DB
7-
from acmapi.models import \
8-
Event, Post, Person, Membership, Officership
7+
from acmapi.models import Event
8+
from acmapi.models import Post
9+
from acmapi.models import Person
10+
from acmapi.models import Membership
11+
from acmapi.models import Officership
912
import unittest
1013
import os
1114
import datetime
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import unittest
2+
3+
import json
4+
5+
from datetime import datetime
6+
7+
from flask import Flask
8+
from flask.ext.restful import marshal
9+
10+
from acmapi.field_types import Date
11+
12+
from freezegun import freeze_time
13+
14+
DATE_FORMAT = '%Y-%m-%d'
15+
16+
class test_date_field_type(unittest.TestCase):
17+
18+
def setUp(self):
19+
self.app = Flask(__name__)
20+
21+
@freeze_time("2012-01-14 12:00:01")
22+
def test_valid(self):
23+
24+
field = {
25+
'date': Date(DATE_FORMAT),
26+
}
27+
28+
with self.app.test_request_context():
29+
30+
self.assertEqual(
31+
dict(marshal({'date': datetime.now()}, field)),
32+
{'date': "2012-01-14"})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import unittest
2+
3+
import json
4+
5+
from datetime import datetime
6+
7+
from flask import Flask
8+
from flask.ext.restful import marshal
9+
from flask.ext.restful.fields import MarshallingException
10+
11+
from acmapi.field_types import DateTime
12+
13+
from freezegun import freeze_time
14+
15+
16+
DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S.%f"
17+
18+
class test_date_field_type(unittest.TestCase):
19+
20+
def setUp(self):
21+
self.app = Flask(__name__)
22+
23+
@freeze_time("2012-01-14 12:00:01")
24+
def test_valid(self):
25+
26+
field = {
27+
'date': DateTime(DATETIME_FORMAT),
28+
}
29+
30+
with self.app.test_request_context():
31+
32+
self.assertEqual(
33+
dict(marshal({'date': datetime.now()}, field)),
34+
{'date': "2012-01-14 12:00:01.000000"})

0 commit comments

Comments
 (0)