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
9 changes: 9 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[flake8]
max-line-length = 88
# E203: whitespace before ':' — black formats slices this way
# E402: module level import not at top — spyne uses logging setup before imports throughout
# W503: line break before binary operator — black preference
extend-ignore = E203, E402, W503
per-file-ignores =
# __init__.py files intentionally re-export symbols from submodules
*/__init__.py: F401, F403, F811
28 changes: 28 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Tests

on:
push:
pull_request:

jobs:
unit-tests:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.10"

- name: Install pipenv
run: pip install pipenv

- name: Install dependencies
run: |
pipenv install --dev
pipenv run pip install .

- name: Run tests
run: pipenv run make test-isolated
18 changes: 18 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
variables:
KUBERNETES_MEMORY_REQUEST_DEFAULT: "2000Mi"
KUBERNETES_MEMORY_LIMIT_DEFAULT: "2000Mi"
CODE_QUALITY: false

include:
- component: $CI_SERVER_FQDN/gitlab-ci/ci-toolbox/python-library@1.0.93


unit-tests:
script:
# Install packages
- pip install pipenv
- pipenv install --dev
- pipenv run pip install .

# Run tests
- pipenv run make test-isolated
16 changes: 16 additions & 0 deletions .pydev/base.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
VERSION_FILES=pyproject.toml

ALIASES = '{
"install": {
"command": "sh \"pipenv install && pipenv run pip install .\"",
"description": "Build docker image with dependencies and run migrations"
},
"test": {
"command": "sh \"pipenv run pytest\"",
"description": "Run tests"
},
"reformat": {
"command": ["sh \"isort src\"", "sh \"autoflake -i --remove-all-unused-imports --recursive src\"", "sh \"black src\""],
"description": "Reformat code"
}
}'
21 changes: 21 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.PHONY: test test-isolated

test:
pytest spyne/test/ -x -q

test-isolated:
@failed_files=""; \
for f in $$(find spyne/test -name 'test_*.py' | sort); do \
printf '\n=== %s ===\n' "$$f"; \
if ! pytest $$f -x --tb=short -q 2>&1; then \
failed_files="$$failed_files $$f"; \
fi; \
done; \
echo ""; \
if [ -z "$$failed_files" ]; then \
echo "All files passed."; \
else \
echo "Failed files:"; \
for f in $$failed_files; do echo " $$f"; done; \
exit 1; \
fi
34 changes: 34 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]

[dev-packages]
pytest = ">=2.9"
pytest-twisted = "*"
pytest-cov = "*"
coverage = "*"
junitxml = "*"
werkzeug = "*"
sqlalchemy = "*"
lxml = ">=3.6"
pyyaml = "*"
pyzmq = "*"
twisted = "*"
colorama = "*"
msgpack = ">=1"
webtest = "*"
pytest-django = "*"
django = "*"
python-subunit = "*"
pyramid = "*"
tox = "*"
pyparsing = ">=2.0.2"
suds-community = "*"
zeep = "*"
pandas = "*"

[requires]
python_version = "3.10"
1,617 changes: 1,617 additions & 0 deletions Pipfile.lock

Large diffs are not rendered by default.

98 changes: 50 additions & 48 deletions examples/authentication/http_cookie/server_soap.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python
#encoding: utf8
# encoding: utf8
#
# Copyright © Burak Arslan <burak at arskom dot com dot tr>,
# Arskom Ltd. http://www.arskom.com.tr
Expand Down Expand Up @@ -36,7 +36,7 @@

from pprint import pformat

from spyne.util.six.moves.http_cookies import SimpleCookie
from http.cookies import SimpleCookie

# bcrypt seems to be among the latest consensus around cryptograpic circles on
# storing passwords.
Expand All @@ -45,7 +45,7 @@
try:
import bcrypt
except ImportError:
print('easy_install --user py-bcrypt to get it.')
print("easy_install --user py-bcrypt to get it.")
raise

from spyne import Unicode, Application, rpc, Service
Expand All @@ -56,46 +56,47 @@


class PublicKeyError(Fault):
__namespace__ = 'spyne.examples.authentication'
__namespace__ = "spyne.examples.authentication"

def __init__(self, value):
super(PublicKeyError, self).__init__(
faultcode='Client.KeyError',
faultstring='Value %r not found' % value
)
faultcode="Client.KeyError", faultstring="Value %r not found" % value
)


class AuthenticationError(Fault):
__namespace__ = 'spyne.examples.authentication'
__namespace__ = "spyne.examples.authentication"

def __init__(self, user_name):
# TODO: self.transport.http.resp_code = HTTP_401

super(AuthenticationError, self).__init__(
faultcode='Client.AuthenticationError',
faultstring='Invalid authentication request for %r' % user_name
)
faultcode="Client.AuthenticationError",
faultstring="Invalid authentication request for %r" % user_name,
)


class AuthorizationError(Fault):
__namespace__ = 'spyne.examples.authentication'
__namespace__ = "spyne.examples.authentication"

def __init__(self):
# TODO: self.transport.http.resp_code = HTTP_401

super(AuthorizationError, self).__init__(
faultcode='Client.AuthorizationError',
faultstring='You are not authorized to access this resource.'
)
faultcode="Client.AuthorizationError",
faultstring="You are not authorized to access this resource.",
)


class UnauthenticatedError(Fault):
__namespace__ = 'spyne.examples.authentication'
__namespace__ = "spyne.examples.authentication"

def __init__(self):
super(UnauthenticatedError, self).__init__(
faultcode='Client.UnauthenticatedError',
faultstring='This resource can only be accessed after authentication.'
)
faultcode="Client.UnauthenticatedError",
faultstring="This resource can only be accessed after authentication.",
)


class SpyneDict(dict):
def __getitem__(self, key):
Expand All @@ -106,65 +107,66 @@ def __getitem__(self, key):


class Preferences(ComplexModel):
__namespace__ = 'spyne.examples.authentication'
__namespace__ = "spyne.examples.authentication"

language = String(max_len=2)
time_zone = String


user_db = {
'neo': bcrypt.hashpw(b'Wh1teR@bbit', bcrypt.gensalt()),
"neo": bcrypt.hashpw(b"Wh1teR@bbit", bcrypt.gensalt()),
}

session_db = set()

preferences_db = SpyneDict({
'neo': Preferences(language='en', time_zone='Underground/Zion'),
'smith': Preferences(language='xx', time_zone='Matrix/Core'),
})
preferences_db = SpyneDict(
{
"neo": Preferences(language="en", time_zone="Underground/Zion"),
"smith": Preferences(language="xx", time_zone="Matrix/Core"),
}
)


class Encoding:
SESSION_ID = 'ascii'
USER_NAME = PASSWORD = CREDENTIALS = 'utf8'
SESSION_ID = "ascii"
USER_NAME = PASSWORD = CREDENTIALS = "utf8"


class UserService(Service):
__tns__ = 'spyne.examples.authentication'
__tns__ = "spyne.examples.authentication"

@rpc(M(Unicode), M(Unicode), _throws=AuthenticationError)
@rpc(M(Unicode), M(Unicode), _throws=AuthenticationError)
def authenticate(ctx, user_name, password):
ENC_C = Encoding.CREDENTIALS
ENC_SID = Encoding.SESSION_ID

password_hash = user_db.get(user_name, None)

if password_hash is None:
raise AuthenticationError(user_name)
raise AuthenticationError(user_name)

password_b = password.encode(ENC_C)
if bcrypt.hashpw(password_b, password_hash) != password_hash:
raise AuthenticationError(user_name)
raise AuthenticationError(user_name)

session_id = '%x' % (random.randint(1<<128, (1<<132)-1))
session_id = "%x" % (random.randint(1 << 128, (1 << 132) - 1))
session_key = (
user_name.encode(ENC_C),
session_id.encode(ENC_SID),
)
session_db.add(session_key)

cookie = SimpleCookie()
cookie["session-id"] = \
base64.urlsafe_b64encode(b"\0".join(session_key)) \
.decode('ascii') # find out how to do urlsafe_b64encodestring
cookie["session-id"] = base64.urlsafe_b64encode(b"\0".join(session_key)).decode(
"ascii"
) # find out how to do urlsafe_b64encodestring

cookie["session-id"]["max-age"] = 3600
header_name, header_value = cookie.output().split(":", 1)
ctx.transport.resp_headers[header_name] = header_value.strip()

logging.debug("Response headers: %s", pformat(ctx.transport.resp_headers))


@rpc(M(String), _throws=PublicKeyError, _returns=Preferences)
def get_preferences(ctx, user_name):
# Only allow access to the users own preferences.
Expand Down Expand Up @@ -193,8 +195,7 @@ def _on_method_call(ctx):

session_cookie = cookie["session-id"].value

user_name, session_id = base64.urlsafe_b64decode(session_cookie) \
.split(b"\0", 1)
user_name, session_id = base64.urlsafe_b64decode(session_cookie).split(b"\0", 1)

session_id = tuple(base64.urlsafe_b64decode(session_cookie).split(b"\0", 1))
if not session_id in session_db:
Expand All @@ -203,26 +204,27 @@ def _on_method_call(ctx):
ctx.udc = session_id[0].decode(Encoding.USER_NAME)


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

if __name__=='__main__':
if __name__ == "__main__":
from spyne.util.wsgi_wrapper import run_twisted

logging.basicConfig(level=logging.DEBUG)
logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG)
logging.getLogger('twisted').setLevel(logging.DEBUG)

application = Application([UserService],
tns='spyne.examples.authentication',
in_protocol=Soap11(validator='lxml'),
out_protocol=Soap11()
logging.getLogger("spyne.protocol.xml").setLevel(logging.DEBUG)
logging.getLogger("twisted").setLevel(logging.DEBUG)

application = Application(
[UserService],
tns="spyne.examples.authentication",
in_protocol=Soap11(validator="lxml"),
out_protocol=Soap11(),
)

wsgi_app = WsgiApplication(application)
wsgi_app.doc.wsdl11.xsl_href = "wsdl-viewer.xsl"

twisted_apps = [
(wsgi_app, b'app'),
(wsgi_app, b"app"),
]

sys.exit(run_twisted(twisted_apps, 8000))
3 changes: 1 addition & 2 deletions examples/custom_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@


from spyne import ComplexModel, AnyDict, ValidationError, Array, Any
from spyne.util import six
from spyne.util.dictdoc import json_loads
from spyne.util.web import log_repr

Expand All @@ -41,7 +40,7 @@ class DictOfUniformArray(AnyDict):
@staticmethod
def validate_native(cls, inst):
for k, v in inst.items():
if not isinstance(k, six.string_types):
if not isinstance(k, str):
raise ValidationError(type(k), "Invalid key type %r")
if not isinstance(v, list):
raise ValidationError(type(v), "Invalid value type %r")
Expand Down
Loading