Skip to content

Commit

Permalink
0.1.0.dev2
Browse files Browse the repository at this point in the history
  • Loading branch information
cybernetlab committed Apr 21, 2015
1 parent f14aceb commit 00012c1
Show file tree
Hide file tree
Showing 12 changed files with 203 additions and 22 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
Changelog
=========

0.1.0.dev2
----------

* API.places.inside now works correctly with cross-tile boundaries
* Minimal logging functionality added
* Minor fixes

0.1.0.dev1
----------

Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ To run tests execute:
TODO
----

1. Logging
1. Logging (in progress)
2. Doc comments in sources
3. Enlage test coverage

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.0.dev1
0.1.0.dev2
Binary file added dist/wikimapia_api-0.1.0.dev2.tar.gz
Binary file not shown.
4 changes: 2 additions & 2 deletions tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ def without_resource_warnings(func):
workaround for Python3 ResourceWarning
http://stackoverflow.com/a/21500796
'''
def wrapper(*args):
def wrapper(*args, **kw):
with warnings.catch_warnings():
warnings.simplefilter('ignore', ResourceWarning)
func(*args)
func(*args, **kw)
return wrapper if PY3 else func
5 changes: 4 additions & 1 deletion tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from .helpers import without_resource_warnings

class TestConfig(unittest.TestCase):
# TODO: test log and log_level

def setUp(self):
self.config = Config()

Expand All @@ -18,7 +20,8 @@ def test_init(self):
expect(config.delay).to.equal(Decimal(5000))

def test_dir(self):
properties = ['key', 'url', 'language', 'delay', 'compression']
properties = ['key', 'url', 'language', 'delay', 'compression',
'log', 'log_level']
expect(all(x in properties for x in dir(self.config))).to.be.ok

def test_key(self):
Expand Down
8 changes: 4 additions & 4 deletions tests/test_place_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from wikimapia_api.api import API
import wikimapia_api.place_api
from wikimapia_api.list_result import ListResult
from wikimapia_api.list_result import ListResult, ListsResult
from .helpers import mock_request, mock_list_request, without_resource_warnings

class TestPlaceAPI(unittest.TestCase):
Expand All @@ -25,11 +25,11 @@ def test_getbyid(self):
@httpretty.activate
@without_resource_warnings
def test_inside(self):
# TODO: full testing here
mock_list_request('place.getbyarea')
l = API.places.inside(10, 20, 30, 40)
expect(l).to.be.a(ListResult)
expect(len(l)).to.equal(3)
expect(l[0]['id']).to.equal(1691851)
expect(l).to.be.a(ListsResult)
expect(len(l)).to.equal(810)

@httpretty.activate
@without_resource_warnings
Expand Down
32 changes: 29 additions & 3 deletions wikimapia_api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import zlib

from .config import Config
from .logger import Logger
from .errors import FunctionNameError, RequestError, UnimplementedError

# Workaround for python.future issue:
Expand All @@ -33,6 +34,9 @@ def config(cls, value):
elif isinstance(value, dict):
cls._config = cls._config.merge(**value)

def log(cls, *args, **kw):
cls._logger.log(*args, **kw)

def __getattribute__(cls, name):
if name != u'_api' and name in cls._api:
(api_class, api, attr) = cls._api[name]
Expand Down Expand Up @@ -64,6 +68,7 @@ def clear_entire_cache(cls):

class API(with_metaclass(_APIMeta, object)):
_config = Config()
_logger = Logger(_config)
_api = dict()
_last_call = None

Expand All @@ -86,6 +91,8 @@ def valid_params(self, function):
raise UnimplementedError('Illegal call to unimplemented valid_params')

def request(self, function, params={}):
API.log('function: {0}, params: {1}'.format(function, str(params)),
level='debug', src='request')
if not isinstance(function, basestring):
return None
# merge config
Expand Down Expand Up @@ -114,7 +121,10 @@ def request(self, function, params={}):
delay = float(now - API._last_call + config.delay)
time.sleep(delay / 1000.0)
try:
conn.request(u'GET', uri.path + u'?' + params)
request = uri.path + u'?' + params
API.log('GET ' + uri.netloc + request,
level='info', src='request')
conn.request(u'GET', request)
response = conn.getresponse()
except http.client.HTTPException:
API._last_call = int(round(time.time() * 1000))
Expand All @@ -130,20 +140,36 @@ def request(self, function, params={}):
result = json.loads(data.decode(u'utf-8'))
conn.close()
if isinstance(result, dict) and u'debug' in result:
# Code: 1708. Message: Maximum number (10000) of objects per area reached. Request smaller area to get other places
if result[u'debug'][u'code'] == 1004:
# API key limit exceeded
API.log('Key limit exceeded', level='warn', src='request')
time.sleep(5)
elif result[u'debug'][u'code'] == 1012:
# IP address limit has been reached
API.log('IP address limit has been reached',
level='warn', src='request')
time.sleep(60)
elif result[u'debug'][u'code'] == 1001:
# Function not found
API.log('Wrong API function {0} requested'.format(function),
level='error', src='request')
raise FunctionNameError(result[u'debug'][u'message'], 1001)
else:
# Other errors
raise RequestError(result[u'debug'][u'message'],
result[u'debug'][u'code'])
c = result[u'debug'][u'code']
m = result[u'debug'][u'message']
API.log(
'Error received. Code: {0}. Message: {1}'.format(c, m),
level='error', src='request'
)
raise RequestError(m, c)
else:
return result

def count_array(self, function, params={}):
API.log('function: {0}, params: {1}'.format(function, str(params)),
level='debug', src='count_array')
params[u'page'] = 1
count = None
if u'count' in params:
Expand Down
31 changes: 29 additions & 2 deletions wikimapia_api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
_KEY_RE = re.compile(u'[0-9A-F]*\Z')
_URL_RE = re.compile(u'\/+\Z')

LOG_LEVELS = ('none', 'error', 'warn', 'info', 'debug')

class Config(object):
def __init__(self, **opts):
self.reset()
Expand All @@ -25,9 +27,12 @@ def reset(self):
self._language = u'en'
self._delay = Decimal(3000)
self._compression = True
self._log_level = 'none'
self._log = False

def __dir__(self):
return [u'key', u'url', u'language', u'delay', u'compression']
return [u'key', u'url', u'language', u'delay', u'compression',
u'log_level', u'log']

@property
def key(self):
Expand Down Expand Up @@ -79,7 +84,7 @@ def compression(self):
return self._compression
@compression.setter
def compression(self, value):
self._compression = value != False
self._compression = value is not False

def merge(self, **opts):
if not opts:
Expand All @@ -92,3 +97,25 @@ def merge(self, **opts):
setattr(config, key, getattr(self, key))
return config

@property
def log_level(self):
return self._log_level
@log_level.setter
def log_level(self, value):
if isinstance(value, basestring):
value = value.lower()
if not value in LOG_LEVELS:
return
self._log_level = value

@property
def log(self):
return self._log
@log.setter
def log(self, value):
if value is False or value is None:
self._log = False
return
if not isinstance(value, basestring):
return
self._log = value
32 changes: 27 additions & 5 deletions wikimapia_api/list_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,12 @@ def __getitem__(self, key):
key += length
if key < 0 or key > length:
raise TypeError('Key out of bounds')
if self.page == 0 or key // self.page_size == self.page - 1:
#print(self.page, key, self.page_size)
if self.page == 0 or key // self.page_size == self.page - 2:
if not self.buffer:
self.__next_page()
if not self.buffer:
return None
if not self.buffer:
return None
return self.buffer[key]
result = self.__load_page(key // self.page_size + 1)
if result is None:
Expand All @@ -66,8 +67,8 @@ def __getitem__(self, key):
def __next__(self):
if not self.buffer:
self.__next_page()
if not self.buffer:
raise StopIteration
if not self.buffer:
raise StopIteration
return self.buffer.pop(0)

def __load_page(self, page):
Expand All @@ -92,7 +93,28 @@ def __next_page(self):
self.loaded += int(result['count'])
if self.total == 0:
self.total = int(result['found'])
if self.page == 0:
self.page = 1
self.page += 1
self.buffer += result[self.key]
if self.page_specified:
self.max = self.loaded

class ListsResult(object):
def __init__(self, results):
self._results = results
self._current = iter([])
self._iter = iter(results)

def __iter__(self):
return self

def __len__(self):
return sum([len(r) for r in self._results])

def __next__(self):
try:
return next(self._current)
except StopIteration:
self._current = next(self._iter)
return next(self._current)
31 changes: 31 additions & 0 deletions wikimapia_api/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# coding: utf-8

from __future__ import (absolute_import, division, print_function,
unicode_literals)
from future.utils import with_metaclass
from past.builtins import basestring
from builtins import dict
from io import open
import time

from .config import Config, LOG_LEVELS
from .errors import FunctionNameError, RequestError, UnimplementedError

class Logger(object):
def __init__(self, config):
if not isinstance(config, Config):
raise TypeError('Wrong config argument')
self._config = config

def log(self, message, level=None, src=None):
if not level in LOG_LEVELS:
return
log_level = LOG_LEVELS.index(level)
if log_level <= 0 or log_level > LOG_LEVELS.index(self._config.log_level):
return
if isinstance(src, basestring):
message = src + ': ' + message
message = message.replace('\n', '').replace('\r', '')
t = time.strftime('%Y-%m-%d %H:%M:%S')
with open(self._config.log, 'at') as f:
f.write('{0} [{1:<5s}] {2}\n'.format(t, level, message))
Loading

0 comments on commit 00012c1

Please sign in to comment.