Skip to content

Commit c2a0cfc

Browse files
author
pavan-maddula
authored
Merge pull request #75 from deep-compute/error_codes
Error codes support
2 parents 77b8e64 + b853e13 commit c2a0cfc

7 files changed

Lines changed: 207 additions & 19 deletions

File tree

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ deploy:
1616
- LICENSE
1717
- kwikapi/api.py
1818
- kwikapi/__init__.py
19-
name: kwikapi-0.4.9
20-
tag_name: 0.4.9
19+
name: kwikapi-0.5.0
20+
tag_name: 0.5.0
2121
on:
2222
repo: deep-compute/kwikapi
2323
- provider: pypi

README.md

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ True
9999
- Bulk request handling
100100
- KwikAPI Client
101101
- Authentication
102+
- Custom error codes and messages
102103

103104
### Versioning support
104105
Versioning support will be used if user wants different versions of functionality with slightly changed behaviour.
@@ -643,7 +644,7 @@ api.register(Calc(), 'v1')
643644
def make_app():
644645
return tornado.web.Application([
645646
(r'^/api/.*', RequestHandler, dict(api=api)),
646-
])
647+
])
647648
if __name__ == "__main__":
648649
app = make_app()
649650
app.listen(8818)
@@ -722,6 +723,95 @@ auth = b'Bearer %s' % b'key'
722723
headers['Authorization'] = auth
723724
```
724725

726+
### Custom error codes and messages
727+
KwikAPI supports error `codes` and `messages`.
728+
729+
A global map of error messages and error codes are maintained across the KwikAPI. Every error code specifies a unique error message that is possible.
730+
731+
**KwikAPI's default error code:**
732+
733+
| Error | Code |
734+
| ---- | ---- |
735+
| Unknown / Internal Exception | 50000 |
736+
| Duplicate API function | 50001 |
737+
| Unknown API function | 50002 |
738+
| Protocol already exists | 50003 |
739+
| Unknown protocol | 50004 |
740+
| Unknown version | 50005 |
741+
| Unsupported type | 50006 |
742+
| Type not specified | 50007 |
743+
| Unknown version or namespace | 50008 |
744+
| Streaming not supported | 50009 |
745+
| Keyword argument error | 50010 |
746+
| Authentication error | 50011 |
747+
| Non-keyword arguments error | 50012 |
748+
749+
Exceptions raised by API's return default error if not handled which is `50000` by default.
750+
751+
#### Examples
752+
- Response with an error code and message:
753+
> URL:`https://www.example.com/addd?a=10&b=20`
754+
755+
```json
756+
{
757+
"message": "Unknown API Function: \"addd\"",
758+
"code": 50002,
759+
"error": "[(www.example.com) UnknownAPIFunction]",
760+
"success": false
761+
}
762+
```
763+
764+
To return custom error messages and code, developer must raise an exception object with attributes `message` and `code` in it.
765+
766+
- Raising custom error message and error code
767+
```python
768+
import tornado.web
769+
import tornado.ioloop
770+
771+
from kwikapi import API
772+
from kwikapi.tornado import RequestHandler
773+
774+
# custom exception
775+
class CalcError(Exception):
776+
def __init__(self, message="Input error", code=1101):
777+
self.message = message
778+
self.code = code
779+
780+
# Core logic that you want to expose as a service
781+
class Calc(object):
782+
def divide(self, a: int, b: int) -> int:
783+
try:
784+
return a / b
785+
except:
786+
raise CalcError(message="b can't be zero")
787+
788+
# Register BaseCalc with KwikAPI
789+
api = API()
790+
api.register(Calc(), 'v1')
791+
792+
# Passing RequestHandler to the KwikAPI
793+
def make_app():
794+
return tornado.web.Application([
795+
(r'^/api/.*', RequestHandler, dict(api=api)),
796+
])
797+
798+
# Starting the application
799+
if __name__ == "__main__":
800+
app = make_app()
801+
app.listen(8888)
802+
tornado.ioloop.IOLoop.current().start()
803+
```
804+
Response:
805+
> URL:`https://www.example.com/divide?a=10&b=0`
806+
807+
```json
808+
{
809+
"success": false,
810+
"message": "b can't be zero",
811+
"code": 1101,
812+
"error": "[(www.example.com) CalcError]"
813+
}
814+
```
725815
## Run test cases
726816
```bash
727817
$ python3 -m doctest -v README.md

kwikapi/api.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from .exception import DuplicateAPIFunction, UnknownAPIFunction
2121
from .exception import ProtocolAlreadyExists, UnknownProtocol
2222
from .exception import UnsupportedType, TypeNotSpecified
23+
from .exception import KeywordArgumentError
2324

2425
from .utils import get_loggable_params
2526

@@ -343,6 +344,7 @@ def get_api_fn(self, fn_name, version, namespace):
343344
class BaseRequestHandler(object):
344345
PROTOCOLS = PROTOCOLS
345346
DEFAULT_PROTOCOL = DEFAULT_PROTOCOL
347+
DEFAULT_ERROR_CODE = 50000
346348

347349
def __init__(self, api,
348350
default_version=None, default_protocol=DEFAULT_PROTOCOL,
@@ -455,8 +457,11 @@ def _find_request_protocol(self, request):
455457
return self.PROTOCOLS[protocol]
456458

457459
def _handle_exception(self, req, e):
458-
message = e.message if hasattr(e, 'message') else str(e)
459-
message = '[(%s) %s: %s]' % (self.api._id, e.__class__.__name__, message)
460+
message_value = e.message if hasattr(e, 'message') else str(e)
461+
code_value = e.code if hasattr(e, 'code') else self.DEFAULT_ERROR_CODE
462+
error_value = '[(%s) %s]' % (self.api._id, e.__class__.__name__)
463+
success_value = False
464+
message = dict(message=message_value, code=code_value, error=error_value, success=success_value)
460465

461466
_log = req.log if hasattr(req, 'log') else self.log
462467
_log.exception('handle_request_error', message=message,
@@ -469,8 +474,7 @@ def _wrap_stream(self, req, res):
469474
for r in res:
470475
yield dict(success=True, result=r)
471476
except Exception as e:
472-
m = self._handle_exception(req, e)
473-
yield dict(success=False, message=m)
477+
yield self._handle_exception(req, e)
474478

475479
def handle_request(self, request):
476480
if self.api._auth:
@@ -486,7 +490,14 @@ def handle_request(self, request):
486490

487491
# invoke the API function
488492
tcompute = time.time()
489-
result = request.fn(**request.fn_params)
493+
try:
494+
result = request.fn(**request.fn_params)
495+
except TypeError as e:
496+
if 'got an unexpected keyword argument' in str(e):
497+
raise KeywordArgumentError(e.args[0])
498+
else:
499+
raise e
500+
490501
tcompute = time.time() - tcompute
491502

492503
response.headers[TIMING_HEADER] = str(tcompute)
@@ -509,7 +520,7 @@ def handle_request(self, request):
509520

510521
except Exception as e:
511522
m = self._handle_exception(request, e)
512-
response.write(dict(success=False, message=m), protocol)
523+
response.write(m, protocol)
513524

514525
response.flush()
515526
response.close()

kwikapi/auth.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from deeputil import xcode, AttrDict
66

77
from .api import Request
8+
from .exception import AuthenticationError
89

910
class BaseServerAuthenticator:
1011
'''Helps in authenticating a request on the server'''
@@ -16,7 +17,7 @@ def _read_auth(self, req):
1617
auth = req.headers.get('Authorization')
1718
_type, info = auth.split(' ', 1)
1819
if _type.lower() != self.TYPE:
19-
raise Exception('Invalid auth type: %s' % _type) # FIXME: raise exc hierarchy
20+
raise AuthenticationError(_type)
2021

2122
auth = AuthInfo(type=self.TYPE)
2223
auth.header_info = info

kwikapi/client.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from deeputil import Dummy, ExpiringCache
77

88
from .protocols import PROTOCOLS
9-
from .exception import APICallFailed
9+
from .exception import NonKeywordArgumentsError
1010
from .api import PROTOCOL_HEADER, REQUEST_ID_HEADER
1111
from .utils import get_loggable_params
1212

@@ -47,7 +47,6 @@ class Client:
4747
def __init__(self, url, version=None, protocol=DEFAULT_PROTOCOL,
4848
path=None, request='', timeout=None, dnscache=None,
4949
headers=None, auth=None, stream=False, log=DUMMY_LOG):
50-
5150
headers = headers or {}
5251

5352
self._url = url
@@ -127,7 +126,8 @@ def _deserialize_response(data, protocol):
127126
def _extract_response(r):
128127
success = r['success']
129128
if not success:
130-
raise Exception(r['message']) # FIXME: raise proper exc
129+
r.pop('success')
130+
raise Exception(r) # FIXME: raise proper exc
131131
else:
132132
r = r['result']
133133

@@ -145,7 +145,8 @@ def _serialize_params(params, protocol):
145145
return data
146146

147147
def __call__(self, *args, **kwargs):
148-
assert(not args) # FIXME: raise appropriate exception
148+
if args:
149+
raise NonKeywordArgumentsError(args)
149150

150151
if self._path:
151152
# FIXME: support streaming in both directions

0 commit comments

Comments
 (0)