Skip to content

Commit

Permalink
Merge pull request #4 from gulducat/base_url
Browse files Browse the repository at this point in the history
base_url instead of proto + host
  • Loading branch information
gulducat authored May 27, 2020
2 parents 9d0601d + a912b26 commit 871e95e
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 20 deletions.
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ pip install requests basic-api

```python
from basic_api import BasicAPI
api = BasicAPI('example.com')
api = BasicAPI('https://api.example.com')
```

All of the below are equivalent.
They make a GET request to `https://example.com/cool/path`
They make a GET request to `https://api.example.com/cool/path`

```python
api.get('/cool/path')
Expand Down Expand Up @@ -69,13 +69,13 @@ It is not a hard requirement for folks who wish to keep requirements to the bare
but there is no fallback adapter, so either install `requests`
or `basic-api[adapter]` (which includes requests) or pass in some specific adapter.

All keyword arguments aside from `host`, `proto`, and `adapter`
All keyword arguments aside from `base_url` and `adapter`
will be passed into the adapter call.

For example, you may wish to include the same header on all API calls:

```python
api = BasicAPI('example.com', headers={'User-Agent': 'fancy'})
api = BasicAPI('https://api.example.com', headers={'User-Agent': 'fancy'})
```

#### Overlapping kwargs
Expand All @@ -85,15 +85,15 @@ If you include the same keyword in subsequent calls,
preferring the value(s) in the API call, so with

```python
api = BasicAPI('example.com', headers={'User-Agent': 'fancy'})
api = BasicAPI('https://api.example.com', headers={'User-Agent': 'fancy'})
api.get('/cool/path', headers={'User-Agent': 'super fancy'})
```

the `get` call will override the previous `User-Agent` header,
resulting in a "super fancy" user agent, and

```python
api.get('/cool/path', headers={'another', 'header'})
api.get('/cool/path', headers={'another': 'header'})
```

will result in both the original "fancy" user agent _and_ `another` header.
Expand All @@ -113,8 +113,9 @@ as the `adapter`, since it has (mostly) the same interface as `requests` itself.
```python
sesh = requests.Session()
sesh.headers = {'User-Agent': 'my fancy app'}
# sesh.get(something something cookies)
api = BasicAPI('example.com', adapter=sesh)
api = BasicAPI('https://api.example.com', adapter=sesh)
# api.post.something.something.cookies()
api.post('/cool/path')
```

#### Advanced
Expand Down
7 changes: 3 additions & 4 deletions basic_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,16 @@


class BasicAPI:
def __init__(self, host, proto='https://', adapter=ADAPTER, **adapter_kw):
def __init__(self, base_url, adapter=ADAPTER, **adapter_kw):
"""Make API requests as naively as possible.
See https://github.com/gulducat/basic-api/ for readme.
:param host: hostname
:param proto: protocol, default "https://"
:param base_url: base API URL
:param adapter: object that makes the api call (default requests)
:param **adapter_kw: keyword arguments to include in calls
:raises NoAdapterError: if no "requests", and no adapter provided.
"""
self._base_url = proto + host
self._base_url = base_url
if not adapter:
raise exc.NoAdapterError('no "requests", and no adapter provided')
self._adapter = adapter
Expand Down
2 changes: 1 addition & 1 deletion examples/basic_github.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def __init__(self, token=None):
"""Basic GitHub API client.
https://developer.github.com/v3/
"""
super().__init__(host='api.github.com')
super().__init__(base_url='https://api.github.com')
token = os.environ.get('GITHUB_TOKEN', token)
if token:
self._adapter_kw['headers'] = {
Expand Down
1 change: 1 addition & 0 deletions requirements-test.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
flake8
mock
pytest-cov
requests-mock
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from setuptools import setup

VERSION = '0.1.3'
VERSION = '0.2.0'


def load_long_description():
Expand Down
11 changes: 5 additions & 6 deletions test_basic_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@


class TestBasicAPI(TestCase):
host = 'api.example.com'
base_url = 'https://' + host
base_url = 'https://api.example.com'
cool_url = base_url + '/cool/path'

def setUp(self):
self.adapter = Mock()
self.api = BasicAPI(self.host, adapter=self.adapter)
self.api = BasicAPI(self.base_url, adapter=self.adapter)

def test_get(self):
self.api.get()
Expand Down Expand Up @@ -58,7 +57,7 @@ def test_sequential_attrs(self):
self.adapter.get.assert_called_once_with(url=self.cool_url)

def test_duplicate_kw_merge(self):
api = BasicAPI(self.host, adapter=self.adapter,
api = BasicAPI(self.base_url, adapter=self.adapter,
headers={'ua': 'cool app'})
api.get(another='kwarg')
expected = {
Expand All @@ -70,7 +69,7 @@ def test_duplicate_kw_merge(self):
def test_duplicate_kw_override(self):
cool = {'headers': {'ua': 'cool app'}}
cooler = {'headers': {'ua': 'COOLER app'}}
api = BasicAPI(self.host, adapter=self.adapter, **cool)
api = BasicAPI(self.base_url, adapter=self.adapter, **cool)

api.get(**cooler)
self.adapter.get.assert_called_once_with(url=self.base_url, **cooler)
Expand All @@ -82,7 +81,7 @@ def test_duplicate_kw_override(self):

def test_no_adapter(self):
with self.assertRaises(NoAdapterError):
BasicAPI(self.host, adapter=None)
BasicAPI(self.base_url, adapter=None)

def test_no_method(self):
with self.assertRaises(NoMethodError):
Expand Down
70 changes: 70 additions & 0 deletions test_docs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/usr/bin/env python

"""
probably shouldn't be doing this, but rolling my own
super basic doctest helps keep docstrings and readme clean.
it's quite fragile, but i'm ok with that.
"""

import re

import requests # noqa
import requests_mock


def test_docs(requests_mock):
# requests_mock makes sure only this url gets hit.
url = 'https://api.example.com/cool/path'
requests_mock.get(url, text='ok')
requests_mock.post(url, text='ok')

with open('README.md') as f:
lines = f.readlines()

for line in lines:
line = line.strip()
if ' = ' in line or line.startswith('from '):
print('>>> ' + line)
exec(line)
continue

if not line.startswith('api'):
continue

if not line.endswith(')'):
print('>>> ' + line)
exec(line)
continue

print('>>> resp = ' + line)
resp = eval(line)
assert resp.text == 'ok'

# the below is kinda complicated, and not strictly necessary
# since the logic is tested elsewhere, but eh, i wrote it
# so here it remains.

request_kw = re.search(r'(\w+)=(\{.*\})', line)
if not request_kw:
continue

kw, val = request_kw.groups()
print("#", kw, val)
val = eval(val)

if kw == 'json':
json = eval('resp.request.json()')
assert json == val

elif kw == 'headers':
headers = eval('resp.request.headers')
intersection = {}
for k, v in val.items():
if headers[k] == v:
intersection[k] = v
assert intersection == val


if __name__ == '__main__':
with requests_mock.Mocker() as m:
test_docs(m)

0 comments on commit 871e95e

Please sign in to comment.