Skip to content

Commit a29cb7b

Browse files
authored
Merge pull request arskom#663 from plq/master
implement spyne.Ignored / port http subsystem to python 3 / misc features and fixes
2 parents 9b32c06 + d0d2923 commit a29cb7b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+583
-191
lines changed

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ everything except ``pytz`` is optional.
4242
Python version
4343
--------------
4444

45-
Spyne 2.13 supports Python 2.7, 3.6, 3.7 and 3.8.
45+
Spyne 2.13 supports Python 2.7, 3.6, 3.7, 3.8, 3.9 and 3.10.
4646

4747
Libraries
4848
---------

examples/authentication/http_cookie/client_suds.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/usr/bin/env python
1+
#!/usr/bin/env python2
22
#encoding: utf8
33
#
44
# Copyright © Burak Arslan <burak at arskom dot com dot tr>,
@@ -29,6 +29,8 @@
2929
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3030
#
3131

32+
# TODO: python3 port
33+
3234
from suds import WebFault
3335
from suds.client import Client
3436

examples/authentication/http_cookie/server_soap.py

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
import sys
3535
import base64
3636

37+
from pprint import pformat
38+
3739
from spyne.util.six.moves.http_cookies import SimpleCookie
3840

3941
# bcrypt seems to be among the latest consensus around cryptograpic circles on
@@ -46,16 +48,12 @@
4648
print('easy_install --user py-bcrypt to get it.')
4749
raise
4850

49-
from spyne.application import Application
50-
from spyne.decorator import rpc
51-
from spyne.error import ResourceNotFoundError
52-
from spyne.model.complex import ComplexModel
53-
from spyne.model.fault import Fault
54-
from spyne.model.primitive import Mandatory
55-
from spyne.model.primitive import String
51+
from spyne import Unicode, Application, rpc, Service
52+
from spyne import M, ComplexModel, Fault, String
53+
from spyne import ResourceNotFoundError
5654
from spyne.protocol.soap import Soap11
5755
from spyne.server.wsgi import WsgiApplication
58-
from spyne.service import Service
56+
5957

6058
class PublicKeyError(Fault):
6159
__namespace__ = 'spyne.examples.authentication'
@@ -126,33 +124,48 @@ class Preferences(ComplexModel):
126124
})
127125

128126

127+
class Encoding:
128+
SESSION_ID = 'ascii'
129+
USER_NAME = PASSWORD = CREDENTIALS = 'utf8'
130+
131+
129132
class UserService(Service):
130133
__tns__ = 'spyne.examples.authentication'
131134

132-
@rpc(Mandatory.String, Mandatory.String, _returns=None,
133-
_throws=AuthenticationError)
135+
@rpc(M(Unicode), M(Unicode), _throws=AuthenticationError)
134136
def authenticate(ctx, user_name, password):
137+
ENC_C = Encoding.CREDENTIALS
138+
ENC_SID = Encoding.SESSION_ID
139+
135140
password_hash = user_db.get(user_name, None)
136141

137142
if password_hash is None:
138143
raise AuthenticationError(user_name)
139144

140-
if bcrypt.hashpw(password, password_hash) != password_hash:
145+
password_b = password.encode(ENC_C)
146+
if bcrypt.hashpw(password_b, password_hash) != password_hash:
141147
raise AuthenticationError(user_name)
142148

143-
session_id = (user_name, '%x' % random.randint(1<<128, (1<<132)-1))
144-
session_db.add(session_id)
149+
session_id = '%x' % (random.randint(1<<128, (1<<132)-1))
150+
session_key = (
151+
user_name.encode(ENC_C),
152+
session_id.encode(ENC_SID),
153+
)
154+
session_db.add(session_key)
145155

146156
cookie = SimpleCookie()
147-
cookie["session-id"] = base64.urlsafe_b64encode(str(session_id[0]) + "\0" + str(session_id[1]))
157+
cookie["session-id"] = \
158+
base64.urlsafe_b64encode(b"\0".join(session_key)) \
159+
.decode('ascii') # find out how to do urlsafe_b64encodestring
160+
148161
cookie["session-id"]["max-age"] = 3600
149162
header_name, header_value = cookie.output().split(":", 1)
150163
ctx.transport.resp_headers[header_name] = header_value.strip()
151-
from pprint import pprint
152-
pprint(ctx.transport.resp_headers)
164+
165+
logging.debug("Response headers: %s", pformat(ctx.transport.resp_headers))
153166

154167

155-
@rpc(Mandatory.String, _throws=PublicKeyError, _returns=Preferences)
168+
@rpc(M(String), _throws=PublicKeyError, _returns=Preferences)
156169
def get_preferences(ctx, user_name):
157170
# Only allow access to the users own preferences.
158171
if user_name != ctx.udc:
@@ -162,22 +175,32 @@ def get_preferences(ctx, user_name):
162175

163176
return retval
164177

178+
165179
def _on_method_call(ctx):
166180
if ctx.descriptor.name == "authenticate":
167181
# No checking of session cookie for call to authenticate
168182
return
169183

184+
logging.debug("Request headers: %s", pformat(ctx.transport.req_env))
185+
170186
cookie = SimpleCookie()
171187
http_cookie = ctx.transport.req_env.get("HTTP_COOKIE")
172188
if http_cookie:
173189
cookie.load(http_cookie)
190+
174191
if "session-id" not in cookie:
175192
raise UnauthenticatedError()
193+
176194
session_cookie = cookie["session-id"].value
177-
session_id = tuple(base64.urlsafe_b64decode(session_cookie).split("\0", 1))
195+
196+
user_name, session_id = base64.urlsafe_b64decode(session_cookie) \
197+
.split(b"\0", 1)
198+
199+
session_id = tuple(base64.urlsafe_b64decode(session_cookie).split(b"\0", 1))
178200
if not session_id in session_db:
179201
raise AuthenticationError(session_id[0])
180-
ctx.udc = session_id[0] # user name
202+
203+
ctx.udc = session_id[0].decode(Encoding.USER_NAME)
181204

182205

183206
UserService.event_manager.add_listener('method_call', _on_method_call)

examples/flask/manage.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2929
#
3030

31-
from werkzeug.wsgi import DispatcherMiddleware
31+
from werkzeug.middleware.dispatcher import DispatcherMiddleware
32+
3233
from spyne.server.wsgi import WsgiApplication
3334

3435
from apps import spyned

examples/xml/thirdparty_schema.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#!/usr/bin/env python
22

3-
# this doesn't work yet because <union> is not implemented
3+
# !!!!!!!!!1111!!!!!!!!!! ATTENTION !!!!!11111!!!!!!!!!!!!!
4+
# This example is not finished. It doesn't work yet because
5+
# <union> is not implemented
6+
# !!11111!!!!!!!11111!!!! ATTENTION !!!!!one!!!!!11111!!!!!
47

58
from __future__ import print_function
69

@@ -65,8 +68,9 @@ class NS:
6568
from spyne.util.xml import parse_schema_file
6669

6770

68-
for fn in files:
71+
for ns, fn in files.items():
6972
if not isfile(fn):
73+
print("Missing file", fn)
7074
raise Exception("Please run 'make' in this script's directory to fetch"
7175
"schema files before running this example")
7276

examples/xml/utils.py

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232

3333
import logging
34+
3435
logging.basicConfig(level=logging.DEBUG)
3536

3637
import uuid
@@ -39,16 +40,10 @@
3940
from pprint import pprint
4041

4142
from lxml import etree
43+
from lxml.builder import E
4244

43-
from spyne.model.primitive import Uuid
44-
from spyne.model.primitive import Unicode
45-
from spyne.model.primitive import String
46-
from spyne.model.primitive import Integer
47-
from spyne.model.primitive import Decimal
48-
from spyne.model.primitive import DateTime
49-
from spyne.model.complex import XmlData
50-
from spyne.model.complex import ComplexModel
51-
from spyne.model.complex import XmlAttribute
45+
from spyne import AnyXml, Uuid, Unicode, String, Integer, Decimal, DateTime, \
46+
XmlData, ComplexModel, XmlAttribute
5247

5348
from spyne.util.xml import get_schema_documents
5449
from spyne.util.xml import get_object_as_xml
@@ -65,22 +60,27 @@ class Punk(ComplexModel):
6560
d = DateTime
6661

6762

63+
class DateTimeWithAttribute(ComplexModel):
64+
value = XmlData(DateTime)
65+
f = XmlAttribute(Unicode)
66+
67+
6868
class Foo(ComplexModel):
6969
__namespace__ = 'some_other_namespace'
7070

7171
a = String
7272
b = Integer
7373
c = Decimal
74-
d = DateTime
74+
d = DateTimeWithAttribute
7575
e = XmlAttribute(Integer)
76-
f = XmlAttribute(Unicode, attribute_of='d')
7776

7877

7978
class ProductEdition(ComplexModel):
8079
__namespace__ = 'kickass_namespace'
8180

8281
id = XmlAttribute(Uuid)
8382
name = XmlData(Unicode)
83+
addtl = XmlData(AnyXml)
8484

8585

8686
class Product(ComplexModel):
@@ -94,34 +94,52 @@ class Product(ComplexModel):
9494
pprint(docs)
9595
print()
9696

97-
pprint({k: v.attrib['targetNamespace'] for k,v in docs.items()})
97+
pprint({k: v.attrib['targetNamespace'] for k, v in docs.items()})
9898

9999
# the default ns prefix is always tns
100100
print("the default namespace %r:" % docs['tns'].attrib['targetNamespace'])
101-
print(etree.tostring(docs['tns'], pretty_print=True))
101+
print(etree.tostring(docs['tns'], pretty_print=True, encoding='unicode'))
102102
print()
103103

104104
# Namespace prefixes are assigned like s0, s1, s2, etc...
105105
print("the other namespace %r:" % docs['s0'].attrib['targetNamespace'])
106-
print(etree.tostring(docs['s0'], pretty_print=True))
106+
print(etree.tostring(docs['s0'], pretty_print=True, encoding='unicode'))
107107
print()
108108

109109
print("the other namespace %r:" % docs['s2'].attrib['targetNamespace'])
110-
print(etree.tostring(docs['s2'], pretty_print=True))
110+
print(etree.tostring(docs['s2'], pretty_print=True, encoding='unicode'))
111111
print()
112112

113113
# Object serialization and deserialization
114-
foo = Foo(a='a', b=1, c=3.4, d=datetime(2011, 02, 20), e=5, f='f')
114+
foo = Foo(
115+
a='a',
116+
b=1,
117+
c=3.4,
118+
d=DateTimeWithAttribute(value=datetime(2011, 2, 20), f='f'),
119+
e=5,
120+
)
121+
115122
doc = get_object_as_xml(foo, Foo)
116-
print(etree.tostring(doc, pretty_print=True))
123+
print()
124+
print(etree.tostring(doc, pretty_print=True, encoding='unicode'))
117125
print(get_xml_as_object(doc, Foo))
118126
print()
119127

128+
# could be anything, really
129+
elt = E.tag(E.subtag("subdata"))
130+
120131
# XmlData example.
121132
print("Product output (illustrates XmlData):")
122-
product = Product(id=uuid.uuid4(), edition=ProductEdition(id=uuid.uuid4(),
123-
name='My edition'))
124-
print(etree.tostring(get_object_as_xml(product, Product), pretty_print=True))
133+
product = Product(
134+
id=uuid.uuid4(),
135+
edition=ProductEdition(id=uuid.uuid4(), name='My edition', addtl=elt)
136+
)
137+
138+
print()
139+
print(etree.tostring(
140+
get_object_as_xml(product, Product),
141+
pretty_print=True, encoding='unicode'
142+
))
125143

126144
# See http://lxml.de/validation.html to see what this could be used for.
127145
print(get_validation_schema([Punk, Foo]))

setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,8 @@ def run_tests(self):
273273
'Programming Language :: Python :: 3.6',
274274
'Programming Language :: Python :: 3.7',
275275
'Programming Language :: Python :: 3.8',
276+
'Programming Language :: Python :: 3.9',
277+
'Programming Language :: Python :: 3.10',
276278
'Programming Language :: Python :: Implementation :: CPython',
277279
#'Programming Language :: Python :: Implementation :: Jython',
278280
'Programming Language :: Python :: Implementation :: PyPy',

spyne/context.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,12 +426,13 @@ def get_in_protocol(self):
426426

427427

428428
class FakeContext(object):
429-
def __init__(self, app=None, descriptor=None,
429+
def __init__(self, app=None, descriptor=None, in_header=None,
430430
in_object=None, in_error=None, in_document=None, in_string=None,
431431
out_object=None, out_error=None, out_document=None, out_string=None,
432432
in_protocol=None, out_protocol=None):
433433
self.app = app
434434
self.descriptor = descriptor
435+
self.in_header = in_header
435436
self.in_object = in_object
436437
self.in_error = in_error
437438
self.in_document = in_document

spyne/model/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
protocols.
2323
"""
2424

25+
from spyne.model._base import Ignored
2526
from spyne.model._base import ModelBase
2627
from spyne.model._base import PushBase
2728
from spyne.model._base import Null

spyne/model/_base.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,26 @@
4343
from spyne.const.xml import DEFAULT_NS
4444

4545

46+
class Ignored(object):
47+
"""When returned as a real rpc response, this is equivalent to returning
48+
None. However, direct method invocations (and NullServer) get the return
49+
value. It can be used for tests and from hooks."""
50+
51+
__slots__ = ('args', 'kwargs')
52+
53+
def __init__(self, *args, **kwargs):
54+
self.args = args
55+
self.kwargs = kwargs
56+
57+
def __eq__(self, other):
58+
return isinstance(other, Ignored) \
59+
and self.args == other.args and self.kwargs == other.kwargs
60+
61+
def __ne__(self, other):
62+
return not (isinstance(other, Ignored) \
63+
and self.args == other.args and self.kwargs == other.kwargs)
64+
65+
4666
def _decode_pa_dict(d):
4767
"""Decodes dict passed to prot_attrs.
4868

0 commit comments

Comments
 (0)