Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
python-dateutil
simplejson
simplejson
defusedxml
28 changes: 21 additions & 7 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
from setuptools import setup, find_packages
from pathlib import Path

from setuptools import find_packages, setup

ROOT = Path(__file__).parent
README = (ROOT / "README.rst").read_text(encoding="utf-8")
REQUIREMENTS = (ROOT / "requirements.txt").read_text(encoding="utf-8").split()

setup(
name='simpleapi',
version='0.0.9',
version='0.1.0',
description='A simple API-framework to provide an easy to use, consistent and portable client/server-architecture (for django, flask and a lot more).',
long_description=open('README.rst').read(),
long_description=README,
long_description_content_type='text/x-rst',
author='Florian Schlachter',
author_email='flori@n-schlachter.de',
url='http://github.com/flosch/simpleapi/tree/',
url='https://github.com/flosch/simpleapi',
packages=find_packages(),
python_requires='>=3.9',
classifiers=[
'Development Status :: 3 - Alpha',
'Development Status :: 4 - Beta',
'Environment :: Web Environment',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python'
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Topic :: Internet :: WWW/HTTP',
],
zip_safe=False,
test_suite='tests',
install_requires=open("requirements.txt", "r").read().split()
install_requires=REQUIREMENTS,
)
10 changes: 5 additions & 5 deletions simpleapi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# -*- coding: utf-8 -*-

from client import *
from server import *
from message import *
from .client import *
from .server import *
from .message import *

__author__ = 'Florian Schlachter'

VERSION = (0, 0, 9)
VERSION = (0, 1, 0)

def get_version():
version = '%s.%s' % (VERSION[0], VERSION[1])
if VERSION[2]:
version = '%s.%s' % (version, VERSION[2])
return version

__version__ = get_version()
__version__ = get_version()
4 changes: 2 additions & 2 deletions simpleapi/client/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-

from client import *
from dummy import *
from .client import *
from .dummy import *
25 changes: 13 additions & 12 deletions simpleapi/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
__all__ = ('Client', 'ClientException', 'ConnectionException', 'RemoteException', )

import socket
import urllib
import cPickle
import urllib.request, urllib.parse, urllib.error
import pickle
from simpleapi.message import formatters, wrappers

class ClientException(Exception): pass
Expand Down Expand Up @@ -51,33 +51,34 @@ def do_call(**kwargs):

formatter = formatters[self.transport_type](None, None)

for key, value in kwargs.iteritems():
for key, value in kwargs.items():
kwargs[key] = formatter.kwargs(value)

data.update(kwargs)

try:
response = urllib.urlopen(self.ns,
urllib.urlencode(data))
payload = urllib.parse.urlencode(data).encode('utf-8')
response = urllib.request.urlopen(self.ns,
payload)

assert response.getcode() in [200,], \
u'HTTP-Server returned http code %s (expected: 200) ' % \
'HTTP-Server returned http code %s (expected: 200) ' % \
response.getcode()

response_buffer = response.read()
except IOError, e:
except IOError as e:
raise ConnectionException(e)

try:
response = formatter.parse(response_buffer)
except (cPickle.UnpicklingError, EOFError), _:
except (pickle.UnpicklingError, EOFError) as _:
raise ClientException(
u'Couldn\'t unpickle response ' \
'Couldn\'t unpickle response ' \
'data. Did you added "pickle" to the namespace\'s' \
' __features__ list?'
)
except ValueError, e:
raise ConnectionException, e
except ValueError as e:
raise ConnectionException(e)

if response.get('success'):
return response.get('result')
Expand All @@ -95,4 +96,4 @@ def set_version(self, version):

def set_ns(self, ns):
"""changes the URL for the Route's endpoint"""
self.ns = ns
self.ns = ns
6 changes: 3 additions & 3 deletions simpleapi/client/dummy.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def do_call(**kwargs):

formatter = formatters[TRANSPORT_TYPE](None, None)

for key, value in kwargs.iteritems():
for key, value in kwargs.items():
kwargs[key] = formatter.kwargs(value)

data.update(kwargs)
Expand All @@ -44,8 +44,8 @@ def do_call(**kwargs):

try:
response = formatter.parse(response_buffer['result'])
except ValueError, e:
raise ConnectionException, e
except ValueError as e:
raise ConnectionException(e)

if response.get('success'):
return response.get('result')
Expand Down
8 changes: 4 additions & 4 deletions simpleapi/message/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from formatter import *
from wrapper import *
from py2xml import *
from extjs import *
from .formatter import *
from .wrapper import *
from .py2xml import *
from .extjs import *
9 changes: 6 additions & 3 deletions simpleapi/message/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
except ImportError:
try:
from django.utils import simplejson as json
except Exception, e:
except Exception as e:
import simplejson as json

__all__ = ('json', 'SAException')

class SAException(Exception):
def __init__(self, msg=None):
super(Exception, self).__init__()
super(SAException, self).__init__(msg)
self._message = msg

def _get_message(self):
Expand All @@ -23,5 +23,8 @@ def _set_message(self, message):

message = property(_get_message, _set_message)

def __str__(self):
return str(self.message)

def __repr__(self):
return self.message
return str(self.message)
53 changes: 35 additions & 18 deletions simpleapi/message/formatter.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
# -*- coding: utf-8 -*-

import cPickle
from common import json
import pickle
import re
from .common import json

try:
import yaml
has_yaml = True
except ImportError:
has_yaml = False

from py2xml import PythonToXML
from .py2xml import PythonToXML

from sajson import SimpleAPIEncoder, SimpleAPIDecoder
from .sajson import SimpleAPIEncoder, SimpleAPIDecoder

__all__ = ('formatters', 'Formatter')


CALLBACK_RE = re.compile(r'^[A-Za-z_$][0-9A-Za-z_$]*(?:\.[A-Za-z_$][0-9A-Za-z_$]*)*$')


def _validate_jsonp_callback(callback):
if callback is None:
return 'simpleapiCallback'
if isinstance(callback, bytes):
callback = callback.decode('utf-8', errors='strict')
if not isinstance(callback, str) or not CALLBACK_RE.match(callback):
raise ValueError('Invalid JSONP callback name.')
return callback

class FormattersSingleton(object):
"""This singleton takes care of all registered formatters. You can easily
register your own formatter for use in both the Namespace and python client.
Expand All @@ -33,17 +47,16 @@ def register(self, name, formatter, override=False):
the given `name`, you can override by setting `override` to ``True``.
"""
if not isinstance(formatter(None, None), Formatter):
raise TypeError(u"You can only register a Formatter not a %s" % formatter)
raise TypeError("You can only register a Formatter not a %s" % formatter)

if name in self._formatters and not override:
raise AttributeError(u"%s is already a valid format type, try a new name" % name)
raise AttributeError("%s is already a valid format type, try a new name" % name)

self._formatters[name] = formatter

def get_defaults(self):
result = filter(lambda item: getattr(item[1], '__active_by_default__', True),
self._formatters.items())
return dict(result).keys()
result = [item for item in list(self._formatters.items()) if getattr(item[1], '__active_by_default__', True)]
return list(dict(result).keys())

def copy(self):
return dict(**self._formatters)
Expand Down Expand Up @@ -106,13 +119,15 @@ class JSONPFormatter(Formatter):
__mime__ = "application/javascript"

def build(self, value):
func = self.callback or 'simpleapiCallback'
result = u'%(func)s(%(data)s)' % {'func': func.decode("utf-8"), 'data': json.dumps(value)}
return result.encode("utf-8")
func = _validate_jsonp_callback(self.callback)
return '%(func)s(%(data)s)' % {
'func': func,
'data': json.dumps(value, cls=SimpleAPIEncoder),
}

def kwargs(self, value):
def kwargs(self, value, action='build'):
if action == 'build':
return json.dumps(value, cls=SimpleAPIEncoder)
return self.build(value)
elif action == 'parse':
return self.parse(value)

Expand All @@ -135,7 +150,7 @@ def kwargs(self, value, action='build'):
return self.parse(value)

def parse(self, value):
return unicode(value)
return str(value)

class PickleFormatter(Formatter):
"""Formatter for use the cPickle python module which supports python object
Expand All @@ -150,7 +165,7 @@ class PickleFormatter(Formatter):
__active_by_default__ = False

def build(self, value):
return cPickle.dumps(value)
return pickle.dumps(value)

def kwargs(self, value, action='build'):
if action == 'build':
Expand All @@ -159,9 +174,11 @@ def kwargs(self, value, action='build'):
return self.parse(value)

def parse(self, value):
if isinstance(value, unicode):
if not getattr(self, 'allow_unsafe_pickle_input', False):
raise ValueError('Pickle input parsing is disabled by default for security reasons.')
if isinstance(value, str):
value = value.encode("utf-8")
return cPickle.loads(value)
return pickle.loads(value)

class XMLFormatter(Formatter):
__mime__ = "text/xml"
Expand Down
22 changes: 13 additions & 9 deletions simpleapi/message/py2xml.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# -*- coding: utf-8 -*-

from xml.etree import cElementTree as ET
try:
# Prefer hardened parser to reduce XML-based DoS attack surface.
from defusedxml import ElementTree as ET
except ImportError: # pragma: no cover - fallback for minimal environments
from xml.etree import cElementTree as ET
from dateutil.parser import parse

__all__ = ('PythonToXML',)
Expand Down Expand Up @@ -56,7 +60,7 @@ def build_int(self, value):

def build_long(self, value):
element = self.create_item('long')
element.text = str(long(value))
element.text = str(int(value))
return element

def build_float(self, value):
Expand All @@ -83,7 +87,7 @@ def build_tuple(self, value):

def build_dict(self, value):
root = self.create_item('dict')
for key, value in value.iteritems():
for key, value in value.items():
element = self.handle(value)
element.set('name', key)
root.append(element)
Expand All @@ -108,25 +112,25 @@ def parse_time(self, element):

def parse_dict(self, element):
tmp = {}
for item in element.getchildren():
for item in list(element):
tmp[item.get('name')] = self.handle(item, 'parse')
return tmp

def parse_list(self, element):
tmp = []
for item in element.getchildren():
for item in list(element):
tmp.append(self.handle(item, 'parse'))
return tmp

def parse_set(self, element):
tmp = []
for item in element.getchildren():
for item in list(element):
tmp.append(self.handle(item, 'parse'))
return set(tmp)

def parse_tuple(self, element):
tmp = []
for item in element.getchildren():
for item in list(element):
tmp.append(self.handle(item, 'parse'))
return tuple(tmp)

Expand All @@ -140,7 +144,7 @@ def parse_int(self, element):
return int(element.text)

def parse_long(self, element):
return long(element.text)
return int(element.text)

def parse_float(self, element):
return float(element.text)
Expand All @@ -159,4 +163,4 @@ def build(self, value):

def parse(self, value):
root = ET.fromstring(value)
return self.handle(root, op='parse')
return self.handle(root, op='parse')
Loading