Skip to content

Commit

Permalink
Fixes stravalib#6 - Fixing _resolve_url to not assume *nix
Browse files Browse the repository at this point in the history
  • Loading branch information
hozn committed Mar 16, 2014
1 parent e1e586a commit fa9c689
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 72 deletions.
15 changes: 7 additions & 8 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,25 @@ API
Main Interaction
================

This part of the documentation covers the primary API for interacting with the
keepass database.
This part of the documentation covers the primary API for interacting with Strava.

Client
------

.. automodule:: stravalib.client
:synopsis: The maininterface to the v3 REST API.
:members:
:inherited-members:
:show-inheritance:

Model
-----

.. automodule:: stravalib.model
:synopsis: The entity objects that are instantiated from the API.
:members:
:synopsis: The entity objects that are instantiated from the API.
:members:
:show-inheritance:

Errors
------

Expand All @@ -36,7 +35,7 @@ The exception classes raised by the library.
:members:
:inherited-members:
:show-inheritance:

Under-the-Hood
==============

Expand Down
26 changes: 13 additions & 13 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
Documentation
=============

**stravalib** is a python library for interacting with `version 3 <http://http://strava.github.io/api/>`_ of the
**stravalib** is a python library for interacting with `version 3 <http://http://strava.github.io/api/>`_ of the
`Strava <http://www.strava.com>`_ API.

This library is designed provide a simple and easy-to-use object model paradigm for interacting
Expand All @@ -15,9 +15,9 @@ with the API, support modern versions of Python (2.x and 3.x), and expose the fu
**Why use stravalib?** The Strava REST API is fairly straightforward. The main reasons to use something like
stravalib would be:
- Result structs (dicts) are returned as more "strongly typed" model objects.
- Relationships can be traversedon model objects to pull in related content "seamlessly".
- Relationships can be traversedon model objects to pull in related content "seamlessly".
- Units and date/time/durations types are converted to python objects to facilite converting and displaying these values.
- Built-in support for rate limiting and more intelligent error handling.
- Built-in support for rate limiting and more intelligent error handling.

Changelog
---------
Expand All @@ -35,41 +35,41 @@ Getting Started
The package is avialable on PyPI to be installed using easy_install or pip:

.. code-block:: none
shell$ pip install stravalib
Of course, by itself this package doesn't do much; it's a library. So it is more likely that you will
list this package as a dependency in your own `install_requires` directive in `setup.py`. Or you can
Of course, by itself this package doesn't do much; it's a library. So it is more likely that you will
list this package as a dependency in your own `install_requires` directive in `setup.py`. Or you can
download it and explore Strava content in your favorite python REPL.

In order to make use of this library, you will need to have access keys for one or more Strava users.
These access keys can be fetched by using helper methods provided by :class:`stravalib.client.Client` class.
See :ref:`auth` for more details.

Usage
-----

More detailed documentation to get you started
More detailed documentation to get you started

.. toctree::
:maxdepth: 2

usage/auth
usage/overview
usage/athletes

(MORE COMING SOON)

API Reference
-------------

In-depth reference guide for developing software with keepassdb.
In-depth reference guide for developing software with stravalib.

.. toctree::
:maxdepth: 2

api


Indices and tables
==================
Expand Down
6 changes: 5 additions & 1 deletion docs/news.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ Changes

.. contents::

0.2.2
-----
* Fixed the _resolve_url to not assume running on **nix system.
0.2.1
-----
* Changed Activity.gear to be a full entity attribute (Strava API changed)
Expand All @@ -11,7 +15,7 @@ Changes
-----
* Added core functionality for Strava API v3.
* Mostly redesigned codebase based on drastic changes in v3 API.
* Dropped support for API v1, v2 and the "scrape" module.
* Dropped support for API v1, v2 and the "scrape" module.

0.1.0
-----
Expand Down
16 changes: 8 additions & 8 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
use_setuptools()
from setuptools import setup, find_packages

version = '0.2.1'
version = '0.2.2'

news = os.path.join(os.path.dirname(__file__), 'docs', 'news.rst')
news = open(news).read()
Expand All @@ -25,19 +25,19 @@
warnings.warn('No news for this version found.')

long_description = """
stravalib is a Python 2.x and 3.x library that provides a simple API for interacting
stravalib is a Python 2.x and 3.x library that provides a simple API for interacting
with the Strava activity tracking website.
"""

if found_news:
title = 'Changes in %s' % version
long_description += "\n%s\n%s\n" % (title, '-'*len(title))
long_description += found_news
long_description += found_news

setup(
name = "stravalib",
version = version,
author = "Hans Lellelid",
setup(
name = "stravalib",
version = version,
author = "Hans Lellelid",
author_email = "[email protected]",
url = "http://github.com/hozn/stravalib",
license = "Apache",
Expand All @@ -64,5 +64,5 @@
"Topic :: Software Development :: Libraries :: Python Modules",
],
use_2to3=True,
zip_safe=False # Technically it should be fine, but there are issues w/ 2to3
zip_safe=False # Technically it should be fine, but there are issues w/ 2to3
)
84 changes: 42 additions & 42 deletions stravalib/protocol.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Low-level classes for interacting directly with the Strava API webservers.
Low-level classes for interacting directly with the Strava API webservers.
"""
from __future__ import division, absolute_import, print_function, unicode_literals
import abc
Expand All @@ -12,21 +12,21 @@
import requests

from stravalib import exc

class ApiV3(object):
"""
This class is responsible for performing the HTTP requests, rate limiting, and error handling.
"""
__metaclass__ = abc.ABCMeta

server = 'www.strava.com'
api_base = '/api/v3'

def __init__(self, access_token=None, requests_session=None, rate_limiter=None):
"""
Initialize this protocol client, optionally providing a (shared) :class:`requests.Session`
object.
:param access_token: The token that provides access to a specific Strava account.
:param requests_session: An existing :class:`requests.Session` object to use.
"""
Expand All @@ -36,36 +36,36 @@ def __init__(self, access_token=None, requests_session=None, rate_limiter=None):
self.rsession = requests_session
else:
self.rsession = requests.Session()

if rate_limiter is None:
# Make it a dummy function, so we don't have to check if it's defined before
# calling it later
rate_limiter = lambda: None

self.rate_limiter = rate_limiter

def authorization_url(self, client_id, redirect_uri, approval_prompt='auto', scope=None, state=None):
"""
Get the URL needed to authorize your application to access a Strava user's information.
See http://strava.github.io/api/v3/oauth/
:param client_id: The numeric developer client id.
:type client_id: int
:param redirect_uri: The URL that Strava will redirect to after successful (or failed) authorization.
:type redirect_uri: str
:param approval_prompt: Whether to prompt for approval even if approval already granted to app.
Choices are 'auto' or 'force'. (Default is 'auto')
:type approval_prompt: str
:param scope: The access scope required. Omit to imply "public". Valid values are 'public', 'write', 'view_private', 'view_private,write'
:type scope: str
:param state: An arbitrary variable that will be returned to your application in the redirect URI.
:type state: str
:return: The URL to use for authorization link.
:rtype: str
"""
Expand All @@ -80,23 +80,23 @@ def authorization_url(self, client_id, redirect_uri, approval_prompt='auto', sco
params['scope'] = scope
if state is not None:
params['state'] = state

return urlparse.urlunsplit(('https', self.server, '/oauth/authorize', urlencode(params), ''))

def exchange_code_for_token(self, client_id, client_secret, code):
"""
Exchange the temporary authorization code (returned with redirect from strava authorization URL)
for a permanent access token.
:param client_id: The numeric developer client id.
:type client_id: int
:param client_secret: The developer client secret
:type client_secret: str
:param code: The temporary authorization code
:type code: str
:return: The access token.
:rtype: str
"""
Expand All @@ -106,54 +106,54 @@ def exchange_code_for_token(self, client_id, client_secret, code):
token = response['access_token']
self.access_token = token
return token

def _resolve_url(self, url):
if not url.startswith('http'):
url = urlparse.urljoin('https://{0}'.format(self.server), os.path.join(self.api_base, url.strip('/')))
url = urlparse.urljoin('https://{0}'.format(self.server), self.api_base + '/' + url.strip('/'))
return url

def _request(self, url, params=None, files=None, method='GET', check_for_errors=True):
url = self._resolve_url(url)
self.log.info("{method} {url!r} with params {params!r}".format(method=method, url=url, params=params))
if params is None:
params = {}
if self.access_token:
params['access_token'] = self.access_token

methods = {'GET': self.rsession.get,
'POST': functools.partial(self.rsession.post, files=files),
'PUT': self.rsession.put,
'DELETE': self.rsession.delete}

try:
requester = methods[method.upper()]
except KeyError:
raise ValueError("Invalid/unsupported request method specified: {0}".format(method))

raw = requester(url, params=params)
if check_for_errors:
self._handle_protocol_error(raw)

resp = raw.json()

# TODO: We should parse the response to get the rate limit details and
# update our rate limiter.
# see: http://strava.github.io/api/#access
# see: http://strava.github.io/api/#access

# At this stage we should assume that request was successful and we should invoke
# our rate limiter. (Note that this may need to be reviewed; some failures may
# also count toward the limit?)
self.rate_limiter()

return resp

def _handle_protocol_error(self, response):
"""
Parses the raw response from the server, raising a :class:`stravalib.exc.Fault` if the
server returned an error.
:param response: The response object.
:raises Fault: If the response contains an error.
:raises Fault: If the response contains an error.
"""
error_str = None
try:
Expand All @@ -163,20 +163,20 @@ def _handle_protocol_error(self, response):
else:
if 'message' in json_response or 'errors' in json_response:
error_str = '{0}: {1}'.format(json_response.get('message', 'Undefined error'), json_response.get('errors'))

x = None
if 400 <= response.status_code < 500:
x = requests.exceptions.HTTPError('%s Client Error: %s [%s]' % (response.status_code, response.reason, error_str))
elif 500 <= response.status_code < 600:
x = requests.exceptions.HTTPError('%s Server Error: %s [%s]' % (response.status_code, response.reason, error_str))
elif error_str:
x = exc.Fault(error_str)

if x is not None:
raise x
return response

return response

def _extract_referenced_vars(self, s):
"""
Utility method to find the referenced format variables in a string.
Expand Down Expand Up @@ -205,7 +205,7 @@ def get(self, url, check_for_errors=True, **kwargs):
url = url.format(**kwargs)
params = dict([(k,v) for k,v in kwargs.items() if not k in referenced])
return self._request(url, params=params, check_for_errors=check_for_errors)

def post(self, url, files=None, check_for_errors=True, **kwargs):
"""
Performs a generic POST request for specified params, returning the response.
Expand All @@ -214,7 +214,7 @@ def post(self, url, files=None, check_for_errors=True, **kwargs):
url = url.format(**kwargs)
params = dict([(k,v) for k,v in kwargs.items() if not k in referenced])
return self._request(url, params=params, files=files, method='POST', check_for_errors=check_for_errors)

def put(self, url, check_for_errors=True, **kwargs):
"""
Performs a generic PUT request for specified params, returning the response.
Expand Down

0 comments on commit fa9c689

Please sign in to comment.