Skip to content

Commit 9534e02

Browse files
author
Davide Rizzo
committed
v0.1.0
0 parents  commit 9534e02

File tree

6 files changed

+369
-0
lines changed

6 files changed

+369
-0
lines changed

LICENSE

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Copyright (c) 2013, Metwit Ltd
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions are met:
6+
7+
1. Redistributions of source code must retain the above copyright
8+
notice, this list of conditions and the following disclaimer.
9+
10+
2. Redistributions in binary form must reproduce the above copyright
11+
notice, this list of conditions and the following disclaimer in the
12+
documentation and/or other materials provided with the distribution.
13+
14+
3. Neither the name of Metwit Ltd nor the names of its contributors may be
15+
used to endorse or promote products derived from this software without
16+
specific prior written permission.
17+
18+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21+
DISCLAIMED. IN NO EVENT SHALL METWIT LTD OR CONTRIBUTORS BE LIABLE FOR ANY
22+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

README.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
Metwit API for Python
2+
=====
3+
4+
A Python client for [Metwit weather API](http://metwit.com/developers/).
5+
6+
It's as simple as this:
7+
8+
from metwit import Metwit
9+
10+
weather = Metwit.weather.get(location_lat=45.45,
11+
location_lng=9.18)
12+
13+
Good! Hope it's not raining.
14+
15+
# weather[0] is the real-time weather in a location
16+
if weather[0]['weather']['status'] == 'rainy':
17+
print 'Better take my umbrella with me'
18+
19+
What if I want to authenticate my app?
20+
21+
from metwit import Metwit
22+
23+
CLIENT_ID = '111111'
24+
CLIENT_SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
25+
26+
metwit = Metwit(client_id=CLIENT_ID, client_secret=CLIENT_SECRET)
27+
metwit.token_client_credentials()
28+
29+
metwit.weather.get(location_lat=45.45, location_lng=9.18)
30+
31+
Fine. This will allow me to use credits from my plan and make more API calls.
32+
33+
Posting data
34+
-----
35+
36+
Metwit API plans come with a number of `weather` calls you can make daily. You can overcome the limit by posting data. Every time you post meaningful data to Metwit, your limits will extend.
37+
38+
How? Post a `Metag`:
39+
40+
metag = {
41+
'geo': {
42+
'lat': 45.45,
43+
'lng': 9.18,
44+
}
45+
'weather': {
46+
'status': 'rainy',
47+
},
48+
}
49+
metwit.metags.post(metag)
50+
51+
`geo` is the only mandatory field. As an overview, a `Metag` object may contain weather status, measured data (temperature, pressure, humidity, etc) and sensory info (I feel hot/warm/etc). Detailed reference is available on the [Metwit API documentation page for metags](http://metwit.com/developers/docs/resources/metags/).
52+
53+
Reference
54+
----
55+
56+
All you need is the `Metwit` class.
57+
58+
### class **`metwit.Metwit`**`([client_id], [client_secret], [access_token], [refresh_token])`
59+
60+
*`client_id`* and *`client_secret`* come from the [Developer Dashboard](https://metwit.com/developers/dashboard/). You only need those if you registered an application. You shouldn't include a client secret if you are going to distribute the code of your application (as opposed to application code hosted on a server, or running on your machine, for example).
61+
62+
If you stored an *`access_token`* (and *`refresh_token`*) elsewhere you can pass them to the constructor, otherwise you can make unauthenticated calls, or obtain a token with `get_token()` or one of the shortcut methods.
63+
64+
#### `Metwit.metags`
65+
#### `Metwit.weather`
66+
#### `Metwit.users`
67+
These are the API resources. You can `.get()` and `.post()` these, or get
68+
individual items with the subscript operator (e.g. `Metwit.metag['123456'].get()`).
69+
70+
#### `Metwit.get_token(grant_type, **kwargs)`
71+
Calls the token endpoint to obtain an access token. The `Metwit` object stores the access token for you, so API calls after this will be authenticated.
72+
73+
#### `Metwit.dialog(redirect_uri, [scope], [state], [implicit])`
74+
Returns the URL for the OAuth 2.0 authorization dialog. If you want to act in behalf of the users, you should redirect their browser to this URL.
75+
76+
#### `Metwit.token_auth_code(code, redirect_uri)`
77+
This is a shortcut to `get_token()`. Use it when your users go through the
78+
authorization dialog and you get the authorization code back.
79+
80+
#### `Metwit.token_client_credentials()`
81+
This is a shortcut to `get_token()`. Use it when you just want to query the
82+
weather and don't need to act in behalf of a user.
83+
84+
#### `Metwit.token_password(username, password, [scope])`
85+
This is a shortcut to `get_token()`. Use it when you have the username and
86+
the password of a Metwit user.
87+
88+
#### `Metwit.resource(uri)`
89+
Use this when you have the URI of a resource and need to access it. E.g.
90+
`metwit.resource('/v2/metags/123456/').get()`.

example.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from metwit import Metwit
2+
from requests import HTTPError
3+
4+
5+
# In order to get client id and client secret go to:
6+
# http://metwit.com/developers/dashboard/
7+
CLIENT_ID = '129380294'
8+
CLIENT_SECRET = 'EoylKe-RXIc-D0Gfu0YR-ZzSuD0Xeax4kbcMPIfe'
9+
10+
11+
metwit = Metwit(client_id=CLIENT_ID, client_secret=CLIENT_SECRET)
12+
13+
# this is an unauthenticated call
14+
print metwit.weather.get(location_lat=45.45,
15+
location_lng=9.18)
16+
17+
try:
18+
metwit.token_password(username='foo', password='bar')
19+
except HTTPError as exc:
20+
print "can't authenticate", exc.response.text
21+
22+
# this is an authenticated call
23+
metag = metwit.metags.post(
24+
dict(weather=dict(status="clear"),
25+
geo=dict(lat=45.45, lng=9.18),
26+
)
27+
)
28+
29+
print metwit.resource(metag['user']['resource_uri']).get()

metwit/metwit.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from .rest import Resource, RestApi
2+
3+
4+
class Metwit(RestApi):
5+
#base_url = "https://api.metwit.com/"
6+
#token_url = 'https://api.metwit.com/token/'
7+
dialog_url = "https://metwit.com/oauth/authorize/"
8+
base_url = "http://127.0.0.1:8000/"
9+
token_url = 'http://127.0.0.1:8000/token/'
10+
11+
weather = Resource('/v2/weather/')
12+
metags = Resource('/v2/metags/')
13+
users = Resource('/v2/users/')

metwit/rest.py

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import json
2+
from weakref import WeakValueDictionary
3+
4+
from requests.compat import urlencode, urljoin
5+
import requests
6+
7+
8+
class Resource(object):
9+
"""
10+
A descriptor representing a resource endpoint.
11+
"""
12+
13+
def __init__(self, url):
14+
self.url = url
15+
16+
def __get__(self, obj, kls=None):
17+
if obj is None:
18+
obj = kls()
19+
20+
try:
21+
endpoint = obj._endpoints[self]
22+
except AttributeError:
23+
endpoint = ResourceEndpoint(obj, self)
24+
obj._endpoints = WeakValueDictionary({self: endpoint})
25+
except KeyError:
26+
endpoint = ResourceEndpoint(obj, self)
27+
obj._endpoints[self] = endpoint
28+
return endpoint
29+
30+
31+
class Endpoint(object):
32+
def request_args(self):
33+
args = {}
34+
if self.api.access_token:
35+
args['headers'] = {
36+
'Authorization': 'Bearer %s' % self.api.access_token
37+
}
38+
return args
39+
40+
def get(self, **kwargs):
41+
request_args = self.request_args()
42+
r = requests.get(self.url, params=kwargs, **request_args)
43+
r.raise_for_status()
44+
response = get_json_response(r)
45+
if 'objects' in response and 'meta' in response:
46+
return response['objects']
47+
else:
48+
return response
49+
50+
51+
def post(self, post_data, **kwargs):
52+
request_args = self.request_args()
53+
request_args['headers']['Content-type'] = 'application/json'
54+
json_data = json.dumps(post_data)
55+
r = requests.post(self.url,
56+
data=json_data,
57+
params=kwargs,
58+
**request_args)
59+
r.raise_for_status()
60+
response = get_json_response(r)
61+
return response
62+
63+
64+
def __getitem__(self, key):
65+
return ItemEndpoint(self.api, self.resource, key)
66+
67+
68+
class ItemEndpoint(Endpoint):
69+
70+
def __init__(self, api, resource, item_id):
71+
self.api = api
72+
self.resource = resource
73+
self.item_id = item_id
74+
75+
@property
76+
def url(self):
77+
resource_url = urljoin(self.resource.url,
78+
'%s/' % self.item_id)
79+
return urljoin(self.api.base_url, resource_url)
80+
81+
82+
class ResourceEndpoint(Endpoint):
83+
def __init__(self, api, resource):
84+
self.api = api
85+
self.resource = resource
86+
87+
@property
88+
def url(self):
89+
return urljoin(self.api.base_url, self.resource.url)
90+
91+
92+
class RestApi(object):
93+
"""
94+
API session.
95+
"""
96+
97+
def __init__(self,
98+
client_id=None, client_secret=None,
99+
access_token=None, refresh_token=None):
100+
self.client_id = client_id
101+
self.client_secret = client_secret
102+
self.access_token = access_token
103+
self.refresh_token = refresh_token
104+
105+
def get_token(self, grant_type, **kwargs):
106+
url = self.token_url
107+
form = dict(kwargs,
108+
grant_type=grant_type
109+
)
110+
r = requests.post(
111+
url,
112+
data=form,
113+
headers={
114+
'Accept': 'application/json',
115+
},
116+
auth=(self.client_id, self.client_secret),
117+
)
118+
r.raise_for_status()
119+
120+
r_json = get_json_response(r)
121+
122+
if not r_json:
123+
raise RestApiError(
124+
'Token endpoint did not return a valid JSON object'
125+
)
126+
127+
try:
128+
self.access_token = r_json['access_token']
129+
except KeyError:
130+
raise RestApiError(
131+
'Token endpoint did not return an access_token'
132+
)
133+
134+
try:
135+
self.refresh_token = r_json['refresh_token']
136+
except KeyError:
137+
pass
138+
139+
def dialog(self, redirect_uri, scope=(), state='', implicit=False):
140+
if implicit:
141+
response_type = 'token'
142+
else:
143+
response_type = 'code'
144+
145+
params = dict(
146+
response_type=response_type,
147+
client_id=self.client_id,
148+
redirect_uri=redirect_uri,
149+
scope=' '.join(scope),
150+
state=state,
151+
)
152+
return "%s?%s" % (self.dialog_url, urlencode(params))
153+
154+
def token_auth_code(self, code, redirect_uri):
155+
return self.get_token('authorization_code',
156+
code=code,
157+
redirect_uri=redirect_uri,
158+
client_id=self.client_id,
159+
)
160+
161+
def token_client_credentials(self):
162+
return self.get_token('client_credentials')
163+
164+
def token_password(self, username, password, scope=('post_metag',)):
165+
return self.get_token('password',
166+
username=username,
167+
password=password,
168+
scope=' '.join(scope),
169+
)
170+
171+
def resource(self, uri):
172+
endpoint = Endpoint()
173+
endpoint.api = self
174+
endpoint.url = urljoin(self.base_url, uri)
175+
return endpoint
176+
177+
178+
class RestApiError(Exception):
179+
pass
180+
181+
182+
def get_json_response(r):
183+
# requests < 1.0 compatibility hack
184+
if callable(r.json):
185+
# requests >= 1.0
186+
return r.json()
187+
else:
188+
# older requests
189+
return r.json

setup.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from distutils.core import setup
2+
3+
setup(
4+
name='metwit-weather',
5+
version='0.1.0',
6+
packages=[
7+
'metwit',
8+
],
9+
description='Metwit weather API client library',
10+
author='Davide Rizzo',
11+
author_email='[email protected]',
12+
url='http://github.com/metwit/metwit-python',
13+
classifiers=[
14+
'Development Status :: 5 - Production/Stable',
15+
'Intended Audience :: Developers',
16+
'License :: OSI Approved :: BSD License',
17+
'Programming Language :: Python :: 2.6',
18+
'Programming Language :: Python :: 2.7',
19+
'Topic :: Internet',
20+
]
21+
)

0 commit comments

Comments
 (0)