Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

rpclib-2.8.0 commits #129

Merged
merged 30 commits into from

1 participant

Burak Arslan
Burak Arslan
Owner

No description provided.

Burak Arslan plq was assigned
plq added some commits
Burak Arslan plq clarify license in pypi d92fc47
Burak Arslan plq Merge branch 'master' of github.com:plq/rpclib ef1ae10
Burak Arslan plq fix service tests that were failing due to null transport in Application d441600
Burak Arslan plq now using the new AuxMethodContext class to pass various to the aux m…
…ethods.
4a2e734
Burak Arslan plq fix json protocol deserialization 21101a2
Burak Arslan plq rpclib.protocol.json: log the error on getattr if there's any a46b202
Burak Arslan plq add docstring to wsgimounter 679a178
Burak Arslan plq rpclib.aux: initialize_context function initializes ctx.aux. 610e329
Burak Arslan plq negligible 77c4328
Burak Arslan plq nullserver got prettier. a17d807
Burak Arslan plq rpclib.util.xml.get_object_as_xml: pass no_namespace=True to strip na…
…mespaces out of the xml output.
19683f0
Burak Arslan plq rpclib.aux: remove debug statements 3983a10
Burak Arslan plq changelog update 58fe120
Burak Arslan plq rpclib.aux.thread: Initialize threadpool as late as possible to let t…
…he process fork before spawning threads.
5c17662
Burak Arslan plq replace tabs with 4 spaces / remove trailing space aaa8c06
Burak Arslan plq make httprpc test standalone. 1e88498
Burak Arslan plq reorganize and fix tests, make interop tests mostly standalone.
still issues with running them with a single py.test command though.
e3cf052
Burak Arslan plq remove dead code. 1a90a9a
Burak Arslan plq fix zeromq server caccff1
Burak Arslan plq make optional dependencies really optional 9e155e4
Burak Arslan plq negligible 6e584e4
Burak Arslan plq fix typo. whoops. d771dff
Burak Arslan plq make SelfReference work again. c47195a
Burak Arslan plq ignore packages whose names start with _ in automatic namespace assig…
…nment
5bb1052
Burak Arslan plq get_object_as_xml and get_xml_as_object argument change.
now get_object_as_xml can also get class suggestion.
ab91a3e
Burak Arslan plq examples/helloworld_soap_twisted: fiddled with default namespace pref…
…ix. xmlns:tns="..." is now xmlns="..."
584d4f4
Burak Arslan plq add docstring to InterfaceDocumentBase 5e31435
Burak Arslan plq make _body_style='bare' work. At least in Soap11 05c9dee
Burak Arslan plq pep8 tweak 1d4795c
Burak Arslan plq don't put alias in interface.classes set a08a3de
Burak Arslan
Owner

okay, this goes in. further fixes will be made in following pull requests.

Burak Arslan plq merged commit a08a3de into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on May 8, 2012
  1. Burak Arslan

    clarify license in pypi

    plq authored
  2. Burak Arslan
Commits on May 9, 2012
  1. Burak Arslan
  2. Burak Arslan
  3. Burak Arslan

    fix json protocol deserialization

    plq authored
  4. Burak Arslan
  5. Burak Arslan

    add docstring to wsgimounter

    plq authored
  6. Burak Arslan
  7. Burak Arslan

    negligible

    plq authored
  8. Burak Arslan

    nullserver got prettier.

    plq authored
  9. Burak Arslan

    rpclib.util.xml.get_object_as_xml: pass no_namespace=True to strip na…

    plq authored
    …mespaces out of the xml output.
Commits on May 10, 2012
  1. Burak Arslan
  2. Burak Arslan

    changelog update

    plq authored
  3. Burak Arslan

    rpclib.aux.thread: Initialize threadpool as late as possible to let t…

    plq authored
    …he process fork before spawning threads.
Commits on May 12, 2012
  1. Burak Arslan
  2. Burak Arslan

    make httprpc test standalone.

    plq authored
  3. Burak Arslan

    reorganize and fix tests, make interop tests mostly standalone.

    plq authored
    still issues with running them with a single py.test command though.
  4. Burak Arslan

    remove dead code.

    plq authored
  5. Burak Arslan

    fix zeromq server

    plq authored
  6. Burak Arslan
  7. Burak Arslan

    negligible

    plq authored
  8. Burak Arslan

    fix typo. whoops.

    plq authored
Commits on May 14, 2012
  1. Burak Arslan

    make SelfReference work again.

    plq authored
Commits on May 15, 2012
  1. Burak Arslan
Commits on May 17, 2012
  1. Burak Arslan

    get_object_as_xml and get_xml_as_object argument change.

    plq authored
    now get_object_as_xml can also get class suggestion.
Commits on May 18, 2012
  1. Burak Arslan

    examples/helloworld_soap_twisted: fiddled with default namespace pref…

    plq authored
    …ix. xmlns:tns="..." is now xmlns="..."
  2. Burak Arslan
  3. Burak Arslan
  4. Burak Arslan

    pep8 tweak

    plq authored
  5. Burak Arslan
This page is out of date. Refresh to see the latest.
Showing with 466 additions and 341 deletions.
  1. +3 −1 CHANGELOG.rst
  2. +1 −1  doc/source/manual/highlevel.rst
  3. +1 −1  doc/source/manual/t_and_p.rst
  4. +124 −132 doc/source/manual/validation.rst
  5. +1 −1  examples/authentication/http_cookie/server_soap.py
  6. +5 −0 examples/helloworld_soap_twisted.py
  7. +1 −1  examples/validation.py
  8. +2 −2 setup.py
  9. +1 −0  src/rpclib/__init__.py
  10. +14 −0 src/rpclib/_base.py
  11. +3 −2 src/rpclib/application.py
  12. +1 −1  src/rpclib/aux/__init__.py
  13. +15 −7 src/rpclib/aux/_base.py
  14. +2 −2 src/rpclib/aux/sync.py
  15. +7 −3 src/rpclib/aux/thread.py
  16. +2 −1  src/rpclib/client/_base.py
  17. +16 −2 src/rpclib/const/ansi_color.py
  18. +8 −1 src/rpclib/interface/_base.py
  19. +5 −5 src/rpclib/interface/wsdl/wsdl11.py
  20. +5 −2 src/rpclib/interface/xml_schema/_base.py
  21. +1 −5 src/rpclib/interface/xml_schema/model/complex.py
  22. +9 −2 src/rpclib/model/_base.py
  23. +20 −12 src/rpclib/model/complex.py
  24. +6 −2 src/rpclib/model/primitive.py
  25. +3 −2 src/rpclib/protocol/_base.py
  26. +1 −1  src/rpclib/protocol/html.py
  27. +1 −1  src/rpclib/protocol/http.py
  28. +3 −3 src/rpclib/protocol/json/__init__.py
  29. +12 −9 src/rpclib/protocol/xml/_base.py
  30. +6 −0 src/rpclib/protocol/xml/model/complex.py
  31. +33 −12 src/rpclib/server/null.py
  32. +1 −1  src/rpclib/server/twisted/__init__.py
  33. +4 −1 src/rpclib/server/wsgi.py
  34. +2 −2 src/rpclib/server/zeromq.py
  35. +42 −0 src/rpclib/test/interop/{_test_client_base.py → _test_soap_client_base.py}
  36. +4 −1 src/rpclib/test/interop/server/httprpc_pod_basic.py
  37. +3 −1 src/rpclib/test/interop/server/soap_http_basic.py
  38. +8 −2 src/rpclib/test/interop/server/soap_zeromq.py
  39. +19 −0 src/rpclib/test/interop/test_httprpc.py
  40. +3 −1 src/rpclib/test/interop/{test_client_http.py → test_soap_client_http.py}
  41. +3 −0  src/rpclib/test/interop/{test_client_http_twisted.py → test_soap_client_http_twisted.py}
  42. +4 −1 src/rpclib/test/interop/{test_client_zeromq.py → test_soap_client_zeromq.py}
  43. +5 −23 src/rpclib/test/interop/test_suds.py
  44. +3 −3 src/rpclib/test/test_service.py
  45. +1 −1  src/rpclib/test/wsdl/__init__.py
  46. +28 −28 src/rpclib/test/wsdl/test_bindings.py
  47. +0 −58 src/rpclib/util/__init__.py
  48. +5 −0 src/rpclib/util/wsgi_wrapper.py
  49. +19 −4 src/rpclib/util/xml.py
4 CHANGELOG.rst
View
@@ -18,6 +18,8 @@ rpclib-2.8.0-beta
now ignored.
* Add support for async methods, which execute after the primary user code
returns. Currently, the only async execution method is via threads.
+ * Xml & friends: Start tags are now in the same namespace as the definitions
+ themselves. Intermediate tags are in the parent's namespace, just as before.
* Full change log:
* https://github.com/arskom/rpclib/pull/128
* https://github.com/arskom/rpclib/pull/129
@@ -77,7 +79,7 @@ rpclib-2.5.1-beta
rpclib-2.5.0-beta
-----------------
- * Implemented fanout support for transports and protocols that can handle
+ * Implemented fanout support for transports and protocols that can handle
that.
* Implemented a helper module that generates a Soap/Wsdl 1.1 application in
``rpclib.util.simple``
2  doc/source/manual/highlevel.rst
View
@@ -73,7 +73,7 @@ procedure calls to the outside world. The following is used to wrap that
code:
* **User Methods**:
- User methods are the code that you wrote and decided to use rpclib to
+ User methods are the code that you wrote and decided to use rpclib to
expose to the outside world.
* **Decorators**:
2  doc/source/manual/t_and_p.rst
View
@@ -100,7 +100,7 @@ is stored. Workers are identified by an integer. ::
nullable=False)
The consumer is a :class:`rpclib.server._base.ServerBase` child that receives
-requests by polling the database.
+requests by polling the database.
The transport is for displaying it in the Wsdl. While it's irrelevant here, it's
nice to put it in: ::
256 doc/source/manual/validation.rst
View
@@ -2,7 +2,7 @@
Input Validation
================
-This is necessary in the cases in which you have to ensure that the received
+This is necessary in the cases in which you have to ensure that the received
data comply with a given format, such as:
- a number must be within a certain range
@@ -13,21 +13,22 @@ data comply with a given format, such as:
Data validation can be handled by two subsystems:
XML schema
- such rules are enforced by *lxml*, the underlying XML parsing library
+ such rules are enforced by *lxml*, the underlying XML parsing library
+
"Soft" level
- *rpclib* itself can apply additional checks after the data were validated by
- the layer underneath
+ *rpclib* itself can apply additional checks after the data were validated by
+ the layer underneath
The differences between them are:
-- Soft validation ignores unknown fields, while *lxml* validation rejects
+- Soft validation ignores unknown fields, while *lxml* validation rejects
them.
-- Soft validation doesn't care about namespaces, while *lxml* validation
+- Soft validation doesn't care about namespaces, while *lxml* validation
rejects unexpected namespaces.
- Soft validation works with any transport protocol supported by *rpclib*,
while *lxml* validation only works for XML data (i.e. just SOAP/XML).
-============================== ======== =========
+============================== ======== =========
Criteria lxml soft
============================== ======== =========
Unknown fields reject ignore
@@ -35,32 +36,29 @@ Unknown namespaces reject ignore
Supported transport protocols SOAP/XML any
============================== ======== =========
+.. NOTE::
+ The two validation sybsystems operate independently, you can use either one,
+ but not both at the same time. The validator is indicated when instantiating
+ the protocol: ``validator='soft'`` or ``validator='lxml'``.
+
+ ::
+
+ #using 'soft' validation with HttpRpc
+ application = Application([NameOfMonthService],
+ tns='rpclib.examples.multiprot',
+ in_protocol=HttpRpc(validator='soft'),
+ out_protocol=HttpRpc()
+ )
+
+ #using lxml validation with Soap
+ application = Application([UserService],
+ tns='rpclib.examples.authentication',
+ interface=Wsdl11(),
+ in_protocol=Soap11(validator='lxml'),
+ out_protocol=Soap11()
+ )
-
-.. NOTE::
- The two validation sybsystems operate independently, you can use either one,
- but not both at the same time. The validator is indicated when instantiating
- the protocol: ``validator='soft'`` or ``validator='lxml'``.
-
- ::
-
- #using 'soft' validation with HttpRpc
- application = Application([NameOfMonthService],
- tns='rpclib.examples.multiprot',
- in_protocol=HttpRpc(validator='soft'),
- out_protocol=HttpRpc()
- )
-
- #using lxml validation with Soap
- application = Application([UserService],
- tns='rpclib.examples.authentication',
- interface=Wsdl11(),
- in_protocol=Soap11(validator='lxml'),
- out_protocol=Soap11()
- )
-
-
Simple validation at the XML schema level
@@ -69,11 +67,11 @@ This applies to all the primitive data types, and is suitable for simple logical
conditions.
.. NOTE::
- Constraints applied at this level are reflected in the XML schema itself,
- thus a client that retrieves the WSDL of the service will be able to see
- what the constraints are.
- As it was mentioned in the introduction, such validation is only effective
- in the context of SOAP/XML.
+ Constraints applied at this level are reflected in the XML schema itself,
+ thus a client that retrieves the WSDL of the service will be able to see
+ what the constraints are.
+ As it was mentioned in the introduction, such validation is only effective
+ in the context of SOAP/XML.
Any primitive type
@@ -83,60 +81,60 @@ along with their default values
- ``default = None`` - default value if the input is ``None``
- ``nillable = True`` - if True, the item is optional
-- ``min_occurs = 0`` - set this to 1 to make the type mandatory. Can be set to
+- ``min_occurs = 0`` - set this to 1 to make the type mandatory. Can be set to
any positive integer
-- ``max_occurs = 1`` - can be set to any strictly positive integer. Values
+- ``max_occurs = 1`` - can be set to any strictly positive integer. Values
greater than 1 will imply an iterable of objects as native Python type. Can be
set to ``unbounded`` for arbitrary number of arguments
-
+
.. NOTE::
- As of rpclib-2.8.0, use ``float('inf')`` instead of ``unbounded``.
-
+ As of rpclib-2.8.0, use ``float('inf')`` instead of ``unbounded``.
+
These rules can be combined, the example below illustrates how to create a
mandatory string:
- String(min_occurs=1, min_len=1, nillable=False)
-
+ String(min_occurs=1, min_len=1, nillable=False)
+
Numbers
~~~~~~~
-Integers and other countable numerical data types (i.e. except Float or
-Double) can be compared with specific values, using the following keywords:
+Integers and other countable numerical data types (i.e. except Float or
+Double) can be compared with specific values, using the following keywords:
``ge``, ``gt``, ``le``, ``lt`` (they correspond to >=, >, <=, <) ::
- Integer(ge=1, le=12) #an integer between 1 and 12, i.e. 1 <= x <= 12
- Integer(gt=1, le=42) #1 < x <= 42
-
+ Integer(ge=1, le=12) #an integer between 1 and 12, i.e. 1 <= x <= 12
+ Integer(gt=1, le=42) #1 < x <= 42
+
Strings
~~~~~~~
These can be validated against a regular expression: ::
- String(pattern = "[0-9]+") #must contain at least one digit, digits only
-
-
+ String(pattern = "[0-9]+") #must contain at least one digit, digits only
+
+
Length checks can be enforced as well: ::
- String(min_len = 5, max_len = 10)
- String(max_len = 10) #implicit value for min_len = 0
+ String(min_len = 5, max_len = 10)
+ String(max_len = 10) #implicit value for min_len = 0
Other string-related constraints are related to encoding issues. You can specify
- which encoding the strings must be in
- how to handle the situations in which a string cannot be decoded properly (to
- understand how this works, consult `Python's documentation
+ understand how this works, consult `Python's documentation
<http://docs.python.org/howto/unicode.html>`_ ::
String(encoding = 'win-1251')
String(unicode_errors = 'strict') #could be 'replace' or 'ignore'
-
+
These restrictions can be combined: ::
- String(encoding = 'win-1251', max_len = 20)
- String(min_len = 5, max_len = 20, pattern = '[a-z]')
-
+ String(encoding = 'win-1251', max_len = 20)
+ String(min_len = 5, max_len = 20, pattern = '[a-z]')
+
Possible values
~~~~~~~~~~~~~~~
@@ -144,9 +142,9 @@ Sometimes you may want to allow only a certain set of values, which would be
difficult to describe in terms of an interval. If this is the case, you can
explicitly indicate the set: ::
- Integer(values = [1984, 13, 45, 42])
- Unicode(values = [u"alpha", u"bravo", u"charlie"]) #note the 'u' prefix
-
+ Integer(values = [1984, 13, 45, 42])
+ Unicode(values = [u"alpha", u"bravo", u"charlie"]) #note the 'u' prefix
+
Extending the rules of XML validation
@@ -159,7 +157,7 @@ from ``ModelBase``.
After that, you must apply the relevant changes in the code that generates the
-XML schema, otherwise these attributes will **not** be visible in the output.
+XML schema, otherwise these attributes will **not** be visible in the output.
Examples of how to do that:
https://github.com/arskom/rpclib/tree/master/src/rpclib/interface/xml_schema/model
@@ -170,7 +168,7 @@ https://github.com/arskom/rpclib/tree/master/src/rpclib/interface/xml_schema/mod
Advanced validation
-------------------
-*rpclib* offers several primitives for this purpose, they are defined in
+*rpclib* offers several primitives for this purpose, they are defined in
the **ModelBase** class, from which all the types are derived:
https://github.com/arskom/rpclib/blob/master/src/rpclib/model/_base.py
@@ -181,36 +179,36 @@ These primitives are:
- *validate_native* - invoked after the string is converted to a specific Python
value.
-Since XML is a text file, when you read it - you get a string; thus
-*validate_string* is the first filter that can be applied to such data.
+Since XML is a text file, when you read it - you get a string; thus
+*validate_string* is the first filter that can be applied to such data.
At a later stage, the data can be converted to something else, for example - a
number. Once that conversion occurs, you can apply some additional checks - this
is handled by *validate_native*.
- >>> stringNumber = '123'
- >>> stringNumber
- '123' #note the quotes, it is a string
- >>> number = int(stringNumber)
- >>> number
- 123 #notice the absence of quotes, it is a number
- >>> stringNumber == 123
- False #note quite what one would expect, right?
- >>> number == 123
- True
-
-In the example above, *number* is an actual number and can be validated with
-*validate_native*, whereas *stringNumber* is a string and can be validated by
+ >>> stringNumber = '123'
+ >>> stringNumber
+ '123' #note the quotes, it is a string
+ >>> number = int(stringNumber)
+ >>> number
+ 123 #notice the absence of quotes, it is a number
+ >>> stringNumber == 123
+ False #note quite what one would expect, right?
+ >>> number == 123
+ True
+
+In the example above, *number* is an actual number and can be validated with
+*validate_native*, whereas *stringNumber* is a string and can be validated by
*validate_string*.
-Another case in which you need a native validation would be a sanity check on a
-date. Imagine that you have to verify if a received date complies with the
-*"YYYY-MM-DDThh:mm:ss"* pattern (which is *xs:datetime*). You can devise a
+Another case in which you need a native validation would be a sanity check on a
+date. Imagine that you have to verify if a received date complies with the
+*"YYYY-MM-DDThh:mm:ss"* pattern (which is *xs:datetime*). You can devise a
regular expression that will look for 4 digits (YYYY), followed by a dash, then
by 2 more digits for the month, etc. But such a regexp will happily absorb dates
that have "13" as a month number, even though that doesn't make sense. You can
-make a more complex regexp to deal with that, but it will be very hard to
+make a more complex regexp to deal with that, but it will be very hard to
maintain and debug. The best approach is to convert the string into a datetime
object and then perform all the checks you want.
@@ -224,16 +222,16 @@ We'll have to declare our own class, derived from *Unicode* (which, in turn, is
derived from *SimpleModel*, which inherits from *ModelBase*).::
- class SpecialString(Unicode):
- """Custom string type that prohibis the use of colons"""
-
- @staticmethod
- def validate_string(cls, value):
- """Override the function to enforce our own verification logic"""
- if value:
- if ':' in value:
- return True
- return False
+ class SpecialString(Unicode):
+ """Custom string type that prohibis the use of colons"""
+
+ @staticmethod
+ def validate_string(cls, value):
+ """Override the function to enforce our own verification logic"""
+ if value:
+ if ':' in value:
+ return True
+ return False
@@ -245,62 +243,56 @@ This time both flavours of validation are combined: *validate_string* to see if
it is a number, and then *validate_native* to see if it is prime.
.. NOTE::
- *rpclib* has a primitive type called *Integer*, it is reasonable to use that
- one as a basis for this custom type. *Unicode* is used in this example
- simply because it is an opportunity to show both types of validation
- functions in action. This may be a good academic example, but it is
- certainly not the approach one would use in production code.
-
+ *rpclib* has a primitive type called *Integer*, it is reasonable to use that
+ one as a basis for this custom type. *Unicode* is used in this example
+ simply because it is an opportunity to show both types of validation
+ functions in action. This may be a good academic example, but it is
+ certainly not the approach one would use in production code.
::
- class PrimeNumber(Unicode):
- """Custom integer type that only works with prime numbers"""
-
- @staticmethod
- def validate_string(cls, value):
- """See if it is a number"""
- import re
-
- if re.search("[0-9]+", value):
- return True
- else:
- return False
-
- @staticmethod
- def validate_native(cls, value):
- """See if it is prime"""
-
- #calling a hypothetical function that checks if it is prime
- return IsPrime(value)
+ class PrimeNumber(Unicode):
+ """Custom integer type that only works with prime numbers"""
+ @staticmethod
+ def validate_string(cls, value):
+ """See if it is a number"""
+ import re
+
+ if re.search("[0-9]+", value):
+ return True
+ else:
+ return False
+
+ @staticmethod
+ def validate_native(cls, value):
+ """See if it is prime"""
+
+ #calling a hypothetical function that checks if it is prime
+ return IsPrime(value)
.. NOTE::
- Constraints applied at this level do **not modify** the XML schema itself,
- thus a client that retrieves the WSDL of the service will not be aware of
- these restrictions. Keep this in mind and make sure that validation rules
- that are not visible in the XML schema are documented elsewhere.
-
-.. NOTE::
- When overriding ``validate_string`` or ``validate_native`` in a custom type
- class, the validation functions from the parent class are **not invoked**.
- If you wish to apply those validation functions as well, you must call them
- explicitly.
+ Constraints applied at this level do **not modify** the XML schema itself,
+ thus a client that retrieves the WSDL of the service will not be aware of
+ these restrictions. Keep this in mind and make sure that validation rules
+ that are not visible in the XML schema are documented elsewhere.
+.. NOTE::
+ When overriding ``validate_string`` or ``validate_native`` in a custom type
+ class, the validation functions from the parent class are **not invoked**.
+ If you wish to apply those validation functions as well, you must call them
+ explicitly.
-
Summary
=======
- simple checks can be applied at the XML schema level, you can control:
-
- the length of a string
- the pattern with which a string must comply
- a numeric interval, etc
-
-- *rpclib* can apply arbitrary rules for the validation of input data
+- *rpclib* can apply arbitrary rules for the validation of input data
- *validate_string* is the first applied filter
- *validate_native* is the applied at the second phase
- Override these functions in your derived class to add new validation rules
- The validation functions must return a *boolean* value
- - These rules are **not** shown in the XML schema
+ - These rules are **not** shown in the XML schema
2  examples/authentication/http_cookie/server_soap.py
View
@@ -176,7 +176,7 @@ def _on_method_call(ctx):
if not session_id in session_db:
raise AuthenticationError(session_id[0])
ctx.udc = session_id[0] # user name
-
+
UserService.event_manager.add_listener('method_call', _on_method_call)
5 examples/helloworld_soap_twisted.py
View
@@ -62,6 +62,11 @@ def say_hello(name, times):
if __name__=='__main__':
application = Application([HelloWorldService], 'rpclib.examples.hello.twisted',
interface=Wsdl11(), in_protocol=Soap11(), out_protocol=Soap11())
+
+ application.interface.nsmap[None] = application.interface.nsmap['tns']
+ application.interface.prefmap[application.interface.nsmap['tns']] = None
+ del application.interface.nsmap['tns']
+
wsgi_app = WsgiApplication(application)
print('listening on 0.0.0.0:7789')
2  examples/validation.py
View
@@ -24,7 +24,7 @@ def get_name_of_month(month):
from rpclib.server.wsgi import WsgiApplication
from wsgiref.simple_server import make_server
-host = '127.0.0.1'
+host = '127.0.0.1'
port = 9912
server = make_server(host, port, WsgiApplication(rest))
4 setup.py
View
@@ -17,7 +17,7 @@
tools for creating and publishing web services in python. This package
features on-demand wsdl generation for the published services, a
wsgi-compliant web application, support for complex class structures, binary
-attachments, and a simple framework for creating additional serialization
+attachments, and a simple framework for creating additional serialization
mechanisms.
This project uses lxml as it's XML API, providing full namespace support.
@@ -61,7 +61,7 @@
maintainer='Burak Arslan',
maintainer_email='burak+rpclib@arskom.com.tr',
url='http://github.com/arskom/rpclib',
- license='LGPL',
+ license='LGPL-2.1',
zip_safe=False,
install_requires=[
'pytz',
1  src/rpclib/__init__.py
View
@@ -19,6 +19,7 @@
__version__ = '2.8.0-beta'
+from rpclib._base import AuxMethodContext
from rpclib._base import TransportContext
from rpclib._base import EventContext
from rpclib._base import MethodContext
14 src/rpclib/_base.py
View
@@ -27,6 +27,15 @@
from rpclib.const.xml_ns import DEFAULT_NS
from rpclib.util.oset import oset
+class AuxMethodContext(object):
+ """Generic object that holds information specific to auxiliary methods"""
+ def __init__(self, p_ctx, error):
+ self.p_ctx = p_ctx
+ """Primary context that this method was bound to."""
+
+ self.error = error
+ """Error from primary context (if any)."""
+
class TransportContext(object):
"""Generic object that holds transport-specific context information"""
def __init__(self, transport, type=None):
@@ -88,6 +97,11 @@ def __init__(self, transport):
events, as you'd probably want to separate the event data from the
method data."""
+ self.aux = None
+ """Auxiliary-method specific context. You can use this to share data
+ between auxiliary sessions. This is not set in primary methods.
+ """
+
self.method_request_string = None
"""This is used as a basis on deciding which native method to call."""
5 src/rpclib/application.py
View
@@ -145,7 +145,8 @@ def process_request(self, ctx):
def call_wrapper(self, ctx):
"""This method calls the call_wrapper method in the service definition.
This can be overridden to make an application-wide custom exception
- management."""
+ management.
+ """
return ctx.service_class.call_wrapper(ctx)
@@ -159,5 +160,5 @@ def reinitialize(self):
aux_memo = set()
for s,d in self.interface.method_id_map.values():
if d.aux is not None and not id(d.aux) in aux_memo:
- d.aux.initialize(server, db=self.main)
+ d.aux.initialize(server)
aux_memo.add(id(d.aux))
2  src/rpclib/aux/__init__.py
View
@@ -20,7 +20,7 @@
"""Module that contains backends that process the auxiliary contexts. These
result from non-primary functions in service definitions.
-Classes from this package will have the ``AuxProc`` suffix, short for
+Classes from this package will have the ``AuxProc`` suffix, short for
"Auxiliary Processor". Sounds neat, huh? :)
This package is EXPERIMENTAL. Stay away from it.
22 src/rpclib/aux/_base.py
View
@@ -20,11 +20,14 @@
import logging
logger = logging.getLogger(__name__)
+from rpclib import AuxMethodContext
+
def process_contexts(server, contexts, p_ctx, error=None):
for ctx in contexts:
+ ctx.descriptor.aux.initialize_context(ctx, p_ctx, error)
if error is None or ctx.descriptor.aux.process_exceptions:
- ctx.descriptor.aux.process_context(server, ctx, p_ctx, error)
+ ctx.descriptor.aux.process_context(server, ctx)
class AuxProcBase(object):
@@ -32,11 +35,7 @@ def __init__(self, process_exceptions=False):
self.methods = []
self.process_exceptions = process_exceptions
- def initialize(self):
- pass
-
- def process(self, server, ctx, p_ctx, p_error, *args, **kwargs):
- logger.debug("Executing %r" % ctx.service_class.get_method_id(ctx.descriptor))
+ def process(self, server, ctx, *args, **kwargs):
server.get_in_object(ctx)
if ctx.in_error is not None:
logger.exception(ctx.in_error)
@@ -49,4 +48,13 @@ def process(self, server, ctx, p_ctx, p_error, *args, **kwargs):
server.get_out_string(ctx)
for s in ctx.out_string:
- logger.debug(s)
+ pass
+
+ def process_context(self, server, ctx, p_ctx, p_error):
+ raise NotImplementedError()
+
+ def initialize(self, server):
+ pass
+
+ def initialize_context(self, ctx, p_ctx, error):
+ ctx.aux = AuxMethodContext(p_ctx, error)
4 src/rpclib/aux/sync.py
View
@@ -24,5 +24,5 @@
class SyncAuxProc(AuxProcBase):
- def process_context(self, server, ctx, p_ctx, p_error):
- self.process(server, ctx, p_ctx, p_error)
+ def process_context(self, server, ctx):
+ self.process(server, ctx)
10 src/rpclib/aux/thread.py
View
@@ -28,15 +28,19 @@
class ThreadAuxProc(AuxProcBase):
def __init__(self, pool_size=1):
AuxProcBase.__init__(self)
+
+ self.pool = None
self.__pool_size = pool_size
- self.pool = ThreadPool(pool_size)
@property
def pool_size(self):
return self.__pool_size
- def process_context(self, server, ctx, p_ctx, p_error, *args, **kwargs):
- a = [server, ctx, p_ctx, p_error]
+ def process_context(self, server, ctx, *args, **kwargs):
+ a = [server, ctx]
a.extend(args)
self.pool.apply_async(self.process, a, kwargs)
+
+ def initialize(self, server):
+ self.pool = ThreadPool(self.__pool_size)
3  src/rpclib/client/_base.py
View
@@ -149,7 +149,8 @@ def get_in_object(self, ctx):
type_info = ctx.descriptor.out_message._type_info
- if len(ctx.descriptor.out_message._type_info) == 1: # TODO: Non-Wrapped Object Support
+ # TODO: Non-Wrapped Object Support
+ if len(ctx.descriptor.out_message._type_info) == 1:
wrapper_attribute = type_info.keys()[0]
ctx.in_object = getattr(ctx.in_object, wrapper_attribute, None)
18 src/rpclib/const/ansi_color.py
View
@@ -1,14 +1,22 @@
+DARK_RED = ""
LIGHT_GREEN = ""
LIGHT_RED = ""
+LIGHT_BLUE = ""
END_COLOR = ""
def enable_color():
global LIGHT_GREEN
- LIGHT_GREEN = "\033[92m"
+ LIGHT_GREEN = "\033[1;32m"
global LIGHT_RED
- LIGHT_RED = "\033[91m"
+ LIGHT_RED = "\033[1;31m"
+
+ global LIGHT_BLUE
+ LIGHT_BLUE = "\033[1;34m"
+
+ global DARK_RED
+ DARK_RED = "\033[0;31m"
global END_COLOR
END_COLOR = "\033[0m"
@@ -21,6 +29,12 @@ def disable_color():
global LIGHT_RED
LIGHT_RED = ""
+ global LIGHT_BLUE
+ LIGHT_BLUE = ""
+
+ global DARK_RED
+ DARK_RED = ""
+
global END_COLOR
END_COLOR = ""
9 src/rpclib/interface/_base.py
View
@@ -32,6 +32,7 @@
from rpclib.const.suffix import RESPONSE_SUFFIX
from rpclib.model import ModelBase
+from rpclib.model.complex import Alias
from rpclib.model.complex import ComplexModelBase
@@ -276,6 +277,11 @@ def get_namespace_prefix(self, ns):
return pref
def add_class(self, cls):
+ if issubclass(cls, Alias):
+ if issubclass(cls._target, ComplexModelBase):
+ self.add_class(cls._target)
+ return
+
if self.has_class(cls):
return
@@ -323,6 +329,8 @@ def is_valid_import(self, ns):
class InterfaceDocumentBase(object):
+ """Base class for all interface document implementations"""
+
def __init__(self, interface):
self.interface = interface
@@ -335,7 +343,6 @@ def build_interface_document(self, cls):
raise NotImplementedError('Extend and override.')
-
def get_interface_document(self, cls):
"""This function is called by server transports that try to satisfy the
request for the interface document. This should just return a previously
10 src/rpclib/interface/wsdl/wsdl11.py
View
@@ -405,7 +405,7 @@ def add_bindings_for_methods(self, service, root, service_name,
cb_binding):
pref_tns = self.interface.get_namespace_prefix(service.get_tns())
-
+
def inner(binding):
for method in service.public_methods.values():
operation = etree.Element('{%s}operation' % _ns_wsdl)
@@ -508,11 +508,11 @@ def inner(binding):
mid_header.set('use', 'literal')
binding.append(operation)
-
+
port_type_list = service.get_port_types()
if len(port_type_list) > 0:
for port_type_name in port_type_list:
-
+
# create binding nodes
binding = etree.SubElement(root, '{%s}binding' % _ns_wsdl)
binding.set('name', port_type_name)
@@ -520,7 +520,7 @@ def inner(binding):
transport = etree.SubElement(binding, '{%s}binding' % _ns_soap)
transport.set('style', 'document')
-
+
inner(binding)
else:
@@ -533,7 +533,7 @@ def inner(binding):
transport = etree.SubElement(cb_binding, '{%s}binding' % _ns_soap)
transport.set('style', 'document')
transport.set('transport', self.interface.app.transport)
-
+
inner(cb_binding)
return cb_binding
7 src/rpclib/interface/xml_schema/_base.py
View
@@ -37,11 +37,13 @@
from rpclib.model.primitive import Decimal
from rpclib.model.primitive import String
from rpclib.model.complex import ComplexModelBase
+from rpclib.model.complex import Alias
from rpclib.model.enum import EnumBase
from rpclib.model.fault import Fault
from rpclib.util.odict import odict
from rpclib.interface.xml_schema.model import simple_add
+from rpclib.interface.xml_schema.model.complex import alias_add
from rpclib.interface.xml_schema.model.complex import complex_add
from rpclib.interface.xml_schema.model.fault import fault_add
from rpclib.interface.xml_schema.model.enum import enum_add
@@ -52,6 +54,7 @@
_add_handlers = cdict({
object: lambda interface, cls: None,
+ Alias: alias_add,
SimpleModel: simple_add,
ComplexModelBase: complex_add,
Fault: fault_add,
@@ -161,7 +164,7 @@ def build_validation_schema(self):
def get_schema_node(self, pref):
"""Return schema node for the given namespace prefix."""
- # create schema node
+
if not (pref in self.schema_dict):
schema = etree.Element("{%s}schema" % _ns_xsd, nsmap=self.interface.nsmap)
@@ -171,7 +174,7 @@ def get_schema_node(self, pref):
self.schema_dict[pref] = schema
else:
- schema = self.schema_nodes[pref]
+ schema = self.schema_dict[pref]
return schema
6 src/rpclib/interface/xml_schema/model/complex.py
View
@@ -122,13 +122,9 @@ def complex_add(document, cls):
document.add_element(cls, element)
-
def alias_add(document, cls):
- if not document.has_class(cls._target):
- document.add(cls._target)
-
element = etree.Element('{%s}element' % namespace.xsd)
element.set('name', cls.get_type_name())
- element.set('type', cls._target.get_type_name_ns(document.interface.app))
+ element.set('type', cls._target.get_type_name_ns(document.interface))
document.add_element(cls, element)
11 src/rpclib/model/_base.py
View
@@ -146,8 +146,15 @@ def resolve_namespace(cls, default_ns):
cls.__namespace__ = default_ns
if cls.__namespace__ is None:
- cls.__namespace__ = cls.__module__
-
+ ret = []
+ for f in cls.__module__.split('.'):
+ if f.startswith('_'):
+ break
+ else:
+ ret.append(f)
+
+ cls.__namespace__ = '.'.join(ret)
+
@classmethod
def get_type_name(cls):
"""Returns the class name unless the __type_name__ attribute is defined.
32 src/rpclib/model/complex.py
View
@@ -87,7 +87,8 @@ def describe(self, name, element, app):
class SelfReference(object):
- pass
+ def __init__(self):
+ raise NotImplementedError()
class ComplexModelMeta(type(ModelBase)):
@@ -140,12 +141,17 @@ def __new__(cls, cls_name, cls_bases, cls_dict):
% (cls_name, k))
else:
_type_info = cls_dict['_type_info']
+
if not isinstance(_type_info, TypeInfo):
cls_dict['_type_info'] = TypeInfo(_type_info)
for k, v in _type_info.items():
- if not issubclass(v, ModelBase):
+ if issubclass(v, SelfReference):
+ pass
+
+ elif not issubclass(v, ModelBase):
raise ValueError( (k,v) )
+
elif issubclass(v, Array) and len(v._type_info) != 1:
raise Exception("Invalid Array definition in %s.%s."
% (cls_name, k))
@@ -153,10 +159,10 @@ def __new__(cls, cls_name, cls_bases, cls_dict):
return type(ModelBase).__new__(cls, cls_name, cls_bases, cls_dict)
def __init__(self, cls_name, cls_bases, cls_dict):
- for k in cls_dict:
- if cls_dict[k] is SelfReference:
- cls_dict[k] = self
- self._type_info[k] = self
+ type_info = cls_dict['_type_info']
+ for k in type_info:
+ if type_info[k] is SelfReference:
+ type_info[k] = self
type(ModelBase).__init__(self, cls_name, cls_bases, cls_dict)
@@ -169,8 +175,8 @@ class ComplexModelBase(ModelBase):
def __init__(self, **kwargs):
super(ComplexModelBase, self).__init__()
- # this ugliness is due to sqlalchemy's forcing relevant types for database
- # fields
+ # this ugliness is due to sqlalchemy's forcing of relevant types for
+ # database fields
for k in self.get_flat_type_info(self.__class__).keys():
try:
delattr(self, k)
@@ -257,7 +263,6 @@ def get_members_pairs(cls, inst):
else:
yield (k, [v.to_string(subvalue)])
-
@classmethod
@nillable_dict
def to_dict(cls, value):
@@ -338,7 +343,7 @@ def from_string(cls, string):
def resolve_namespace(cls, default_ns):
if getattr(cls, '__extends__', None) != None:
cls.__extends__.resolve_namespace(cls.__extends__, default_ns)
-
+
ModelBase.resolve_namespace(cls, default_ns)
for k, v in cls._type_info.items():
@@ -378,10 +383,13 @@ def alias(type_name, namespace, target):
cls_dict['__namespace__'] = namespace
cls_dict['__type_name__'] = type_name
- cls_dict['_type_info'] = getattr(target, '_type_info', ())
cls_dict['_target'] = target
- return ComplexModelMeta(type_name, (ClassAlias,), cls_dict)
+ ti = getattr(target, '_type_info', None)
+ if ti is not None:
+ cls_dict['_type_info'] = ti
+
+ return ComplexModelMeta(type_name, (Alias,), cls_dict)
@classmethod
def customize(cls, **kwargs):
8 src/rpclib/model/primitive.py
View
@@ -34,7 +34,6 @@
from collections import deque
-from lxml import etree
from pytz import FixedOffset
from rpclib.model import SimpleModel
@@ -42,6 +41,11 @@
from rpclib.error import ValidationError
from rpclib.error import Fault
+try:
+ from lxml import etree
+except ImportError:
+ pass
+
string_encoding = 'utf8'
_date_pattern = r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})'
@@ -469,7 +473,7 @@ class DateTime(SimpleModel):
class Attributes(SimpleModel.Attributes):
"""Customizable attributes of the :class:`rpclib.model.primitive.DateTime`
type."""
-
+
format = None
"""DateTime format fed to the ``strftime`` function. See:
http://docs.python.org/library/datetime.html?highlight=strftime#strftime-strptime-behavior"""
5 src/rpclib/protocol/_base.py
View
@@ -74,7 +74,7 @@ class ProtocolBase(object):
classes can implement only the required subset of the public methods.
The ProtocolBase class supports the following events:
-
+
* ``before_deserialize``:
Called before the deserialization operation is attempted.
@@ -188,7 +188,8 @@ def set_method_descriptor(self, ctx):
def generate_method_contexts(self, ctx):
call_handles = self.get_call_handles(ctx)
if len(call_handles) == 0:
- raise ResourceNotFoundError('Method %r not found.' % ctx.method_request_string)
+ raise ResourceNotFoundError('Method %r not found.' %
+ ctx.method_request_string)
retval = []
for sc, d in call_handles:
2  src/rpclib/protocol/html.py
View
@@ -111,7 +111,7 @@ def serialize(self, ctx, message):
setattr(result_message, attr_name, ctx.out_object[i])
ctx.out_header_doc = None
- ctx.out_body_doc = self.serialize_impl(result_message_class,
+ ctx.out_body_doc = self.serialize_impl(result_message_class,
result_message)
ctx.out_document = ctx.out_body_doc
2  src/rpclib/protocol/http.py
View
@@ -89,7 +89,7 @@ def set_validator(self, validator):
def create_in_document(self, ctx, in_string_encoding=None):
assert ctx.transport.type.endswith('http'), \
- ("This protocol only works with an http transport, not: %s, (in %r)"
+ ("This protocol only works with an http transport, not: %s, (in %r)"
% (ctx.transport.type, ctx.transport))
ctx.in_document = ctx.transport.req
6 src/rpclib/protocol/json/__init__.py
View
@@ -88,7 +88,7 @@ def create_in_document(self, ctx, in_string_encoding=None):
except JSONDecodeError, e:
raise Fault('Client.JsonDecodeError', repr(e))
- def decompose_incoming_envelope(self, ctx):
+ def decompose_incoming_envelope(self, ctx, message):
"""Sets ``ctx.in_body_doc``, ``ctx.in_header_doc`` and
``ctx.method_request_string`` using ``ctx.in_document``.
"""
@@ -96,7 +96,6 @@ def decompose_incoming_envelope(self, ctx):
# set ctx.in_header
ctx.transport.in_header_doc = None # use an rpc protocol if you want headers.
- # set ctx.in_body
doc = ctx.in_document
ctx.in_header_doc = None
@@ -290,7 +289,8 @@ def get_member_pairs(self, cls, inst):
for k, v in cls._type_info.items():
try:
sub_value = getattr(inst, k, None)
- except: # to guard against e.g. sqlalchemy throwing NoSuchColumnError
+ except Exception, e: # to guard against e.g. sqlalchemy throwing NoSuchColumnError
+ logger.error("Error getting %r: %r" %(k,e))
sub_value = None
if v.Attributes.max_occurs > 1:
21 src/rpclib/protocol/xml/_base.py
View
@@ -32,6 +32,7 @@
from rpclib.model import ModelBase
from rpclib.model.binary import ByteArray
from rpclib.model.binary import Attachment
+from rpclib.model.complex import Alias
from rpclib.model.complex import Array
from rpclib.model.complex import Iterable
from rpclib.model.complex import ComplexModelBase
@@ -44,6 +45,7 @@
from rpclib.protocol.xml.model import base_to_parent_element
from rpclib.protocol.xml.model.binary import binary_to_parent_element
+from rpclib.protocol.xml.model.complex import alias_to_parent_element
from rpclib.protocol.xml.model.complex import complex_to_parent_element
from rpclib.protocol.xml.model.enum import enum_to_parent_element
from rpclib.protocol.xml.model.fault import fault_to_parent_element
@@ -89,28 +91,29 @@ def __init__(self, app=None, validator=None, xml_declaration=True,
self.cleanup_namespaces = cleanup_namespaces
self.serialization_handlers = cdict({
+ AnyXml: xml_to_parent_element,
+ Alias: alias_to_parent_element,
+ Fault: fault_to_parent_element,
+ AnyDict: dict_to_parent_element,
+ EnumBase: enum_to_parent_element,
ModelBase: base_to_parent_element,
ByteArray: binary_to_parent_element,
Attachment: binary_to_parent_element,
ComplexModelBase: complex_to_parent_element,
- Fault: fault_to_parent_element,
- AnyXml: xml_to_parent_element,
- AnyDict: dict_to_parent_element,
- EnumBase: enum_to_parent_element,
})
self.deserialization_handlers = cdict({
+ AnyXml: xml_from_element,
+ Fault: fault_from_element,
+ AnyDict: dict_from_element,
+ EnumBase: enum_from_element,
ModelBase: base_from_element,
ByteArray: binary_from_element,
Attachment: binary_from_element,
ComplexModelBase: complex_from_element,
- Fault: fault_from_element,
- AnyXml: xml_from_element,
- AnyDict: dict_from_element,
- EnumBase: enum_from_element,
- Array: array_from_element,
Iterable: iterable_from_element,
+ Array: array_from_element,
})
self.log_messages = (logger.level == logging.DEBUG)
6 src/rpclib/protocol/xml/model/complex.py
View
@@ -63,6 +63,12 @@ def complex_to_parent_element(prot, cls, value, tns, parent_elt, name=None):
get_members_etree(prot, cls, inst, element)
+@nillable_value
+def alias_to_parent_element(prot, cls, value, tns, parent_elt, name=None):
+ if name is None:
+ name = cls.get_type_name()
+
+ prot.to_parent_element(cls._target, value._target, tns, parent_elt, name)
@nillable_element
def complex_from_element(prot, cls, element):
45 src/rpclib/server/null.py
View
@@ -22,12 +22,19 @@
import logging
logger = logging.getLogger(__name__)
+from rpclib import MethodContext
+from rpclib import AuxMethodContext
from rpclib.client._base import Factory
from rpclib.const.ansi_color import LIGHT_RED
+from rpclib.const.ansi_color import LIGHT_BLUE
from rpclib.const.ansi_color import END_COLOR
-from rpclib._base import MethodContext
from rpclib.server import ServerBase
+_big_header = ('=' * 40) + LIGHT_RED
+_big_footer = END_COLOR + ('=' * 40)
+_small_header = ('-' * 20) + LIGHT_BLUE
+_small_footer = END_COLOR + ('-' * 20)
+
class NullServer(ServerBase):
"""A server that doesn't support any transport at all -- it's implemented
to test services without having to run a server.
@@ -47,7 +54,7 @@ def get_wsdl(self):
return self.app.get_interface_document(self.url)
def set_options(self, **kwargs):
- self.service.in_header = kwargs.get('soapheaders', None)
+ self.service.in_header = kwargs.get('soapheaders', self.service.in_header)
class _FunctionProxy(object):
def __init__(self, server, app):
@@ -74,25 +81,33 @@ def __call__(self, *args, **kwargs):
initial_ctx.transport.type = NullServer.transport
contexts = self.app.in_protocol.generate_method_contexts(initial_ctx)
+
+ cnt = 0
+ retval = None
+ logger.warning( "%s start request %s" % (_big_header, _big_footer) )
+
for ctx in contexts:
- # set logging.getLogger('rpclib.server.null').setLevel(logging.CRITICAL)
+ if cnt == 0:
+ p_ctx = ctx
+ else:
+ ctx.descriptor.aux.initialize_context(ctx, p_ctx, error=None)
+
+ # do logging.getLogger('rpclib.server.null').setLevel(logging.CRITICAL)
# to hide the following
- _header = ('=' * 30) + LIGHT_RED
- _footer = END_COLOR + ('=' * 30)
- logger.warning( "%s start request %s" % (_header, _footer) )
+ logger.warning( "%s start context %s" % (_small_header, _small_footer) )
self.app.process_request(ctx)
- logger.warning( "%s end request %s" % (_header, _footer) )
+ logger.warning( "%s end context %s" % (_small_header, _small_footer) )
if ctx.out_error:
raise ctx.out_error
else:
if len(ctx.descriptor.out_message._type_info) == 0:
- retval = None
+ _retval = None
elif len(ctx.descriptor.out_message._type_info) == 1:
- retval = ctx.out_object[0]
+ _retval = ctx.out_object[0]
# workaround to have the context disposed of when the caller is
# done with the return value. the context is sometimes needed to
@@ -100,19 +115,25 @@ def __call__(self, *args, **kwargs):
# sqlalchemy object bound to a session that's defined in the
# context object).
try:
- retval.__ctx__ = ctx
+ _retval.__ctx__ = ctx
except AttributeError:
# not all objects let this happen. (eg. built-in types
# like str, but they don't need the context anyway).
pass
else:
- retval = ctx.out_object
+ _retval = ctx.out_object
# same as above
try:
- retval[0].__ctx__ = ctx
+ _retval[0].__ctx__ = ctx
except AttributeError:
pass
+ if cnt == 0:
+ retval = _retval
+ cnt += 1
+
+ logger.warning( "%s end request %s" % (_big_header, _big_footer) )
+
return retval
2  src/rpclib/server/twisted/__init__.py
View
@@ -215,7 +215,7 @@ def __handle_wsdl_request(self, request):
request.setHeader(k,v)
return ctx.transport.wsdl
-
+
except Exception, e:
ctx.transport.wsdl_error = e
self.http_transport.event_manager.fire_event('wsdl_exception', ctx)
5 src/rpclib/server/wsgi.py
View
@@ -34,7 +34,10 @@
except ImportError: # Python 3
from urllib.parse import parse_qs
-from werkzeug.formparser import parse_form_data
+try:
+ from werkzeug.formparser import parse_form_data
+except ImportError:
+ pass
from rpclib.server.http import HttpMethodContext
from rpclib.server.http import HttpTransportContext
4 src/rpclib/server/zeromq.py
View
@@ -57,7 +57,7 @@ def serve_forever(self):
while True:
error = None
- initial_ctx = ZmqMethodContext(self.app)
+ initial_ctx = ZmqMethodContext(self)
initial_ctx.in_string = [self.zmq_socket.recv()]
contexts = self.generate_contexts(initial_ctx)
@@ -65,7 +65,7 @@ def serve_forever(self):
if p_ctx.in_error:
p_ctx.out_object = p_ctx.in_error
error = p_ctx.in_error
-
+
else:
self.get_in_object(p_ctx)
42 src/rpclib/test/interop/_test_client_base.py → src/rpclib/test/interop/_test_soap_client_base.py
View
@@ -17,13 +17,55 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
#
+import time
import unittest
from rpclib.model.fault import Fault
from datetime import datetime
+import socket
+
+server_started = {}
+
+def test_port_open(port):
+ host = '127.0.0.1'
+
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.connect((host, port))
+ s.shutdown(2)
+
+ return True
+
+def run_server(server_type):
+ if server_type == 'http':
+ from rpclib.test.interop.server.soap_http_basic import main
+ from rpclib.test.interop.server.soap_http_basic import PORT
+
+ elif server_type == 'zeromq':
+ from rpclib.test.interop.server.soap_zeromq import main
+ from rpclib.test.interop.server.soap_zeromq import PORT
+
+ else:
+ raise ValueError(server_type)
+
+ if server_started.get(PORT, None) is None:
+ def run_server():
+ main()
+
+ import thread
+ thread.start_new_thread(run_server, ())
+
+ # FIXME: Does anybody have a better idea?
+ time.sleep(2)
+
+ server_started[PORT] = test_port_open(PORT)
+
+
class RpclibClientTestBase(object):
+ def setUp(self, server_type):
+ run_server(server_type)
+
def test_echo_boolean(self):
val = True
ret = self.client.service.echo_boolean(val)
5 src/rpclib/test/interop/server/httprpc_pod_basic.py
View
@@ -33,7 +33,7 @@
httprpc_soap_application = Application(services,
'rpclib.test.interop.server.httprpc.pod', HttpRpc(), HttpRpc(), Wsdl11())
-if __name__ == '__main__':
+def main():
try:
from wsgiref.simple_server import make_server
from wsgiref.validate import validator
@@ -47,3 +47,6 @@
except ImportError:
print("Error: example server code requires Python >= 2.5")
+
+if __name__ == '__main__':
+ main()
4 src/rpclib/test/interop/server/soap_http_basic.py
View
@@ -31,13 +31,15 @@
soap_application = Application(services, 'rpclib.test.interop.server',
Soap11(validator='lxml', cleanup_namespaces=True), Soap11(), Wsdl11())
+PORT = 9753
+
def main():
try:
from wsgiref.simple_server import make_server
from wsgiref.validate import validator
wsgi_application = WsgiApplication(soap_application)
- server = make_server('0.0.0.0', 9753, validator(wsgi_application))
+ server = make_server('127.0.0.1', PORT, validator(wsgi_application))
logger.info('Starting interop server at %s:%s.' % ('0.0.0.0', 9753))
logger.info('WSDL is at: /?wsdl')
10 src/rpclib/test/interop/server/soap_zeromq.py
View
@@ -23,8 +23,11 @@
from rpclib.server.zeromq import ZeroMQServer
-if __name__ == '__main__':
- url = "tcp://*:5555"
+PORT = 5555
+
+def main():
+ url = "tcp://127.0.0.1:%d" % PORT
+
logging.basicConfig(level=logging.DEBUG)
logging.getLogger('rpclib.protocol.xml').setLevel(logging.DEBUG)
@@ -37,3 +40,6 @@
logging.info("************************")
server.serve_forever()
+
+if __name__ == '__main__':
+ main()
19 src/rpclib/test/interop/test_httprpc.py
View
@@ -17,6 +17,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
#
+import time
import pytz
import unittest
@@ -31,7 +32,25 @@
from datetime import datetime
+_server_started = False
+
class TestHttpRpc(unittest.TestCase):
+ def setUp(self):
+ global _server_started
+
+ if not _server_started:
+ def run_server():
+ from rpclib.test.interop.server.httprpc_pod_basic import main
+ main()
+
+ import thread
+ thread.start_new_thread(run_server, ())
+
+ # FIXME: Does anybody have a better idea?
+ time.sleep(2)
+
+ _server_started = True
+
def test_404(self):
url = 'http://localhost:9757/404'
try:
4 src/rpclib/test/interop/test_client_http.py → src/rpclib/test/interop/test_soap_client_http.py
View
@@ -20,12 +20,14 @@
import unittest
from rpclib.client.http import HttpClient
-from rpclib.test.interop._test_client_base import RpclibClientTestBase
+from rpclib.test.interop._test_soap_client_base import RpclibClientTestBase
from rpclib.test.interop.server.soap_http_basic import soap_application
from rpclib.util.etreeconv import root_dict_to_etree
class TestRpclibHttpClient(RpclibClientTestBase, unittest.TestCase):
def setUp(self):
+ RpclibClientTestBase.setUp(self, 'http')
+
self.client = HttpClient('http://localhost:9753/', soap_application)
self.ns = "rpclib.test.interop.server._service"
3  src/rpclib/test/interop/test_client_http_twisted.py → ...lib/test/interop/test_soap_client_http_twisted.py
View
@@ -18,12 +18,15 @@
#
from twisted.trial import unittest
+from rpclib.test.interop._test_soap_client_base import run_server
from rpclib.client.twisted import TwistedHttpClient
from rpclib.test.interop.server.soap_http_basic import soap_application
class TestRpclibHttpClient(unittest.TestCase):
def setUp(self):
+ run_server('http')
+
self.ns = "rpclib.test.interop.server._service"
self.client = TwistedHttpClient('http://localhost:9753/', soap_application)
5 src/rpclib/test/interop/test_client_zeromq.py → src/rpclib/test/interop/test_soap_client_zeromq.py
View
@@ -19,12 +19,15 @@
import unittest
-from rpclib.test.interop._test_client_base import RpclibClientTestBase
+from rpclib.test.interop._test_soap_client_base import RpclibClientTestBase
from rpclib.client.zeromq import ZeroMQClient
from rpclib.test.interop.server.soap_http_basic import soap_application
+
class TestRpclibZmqClient(RpclibClientTestBase, unittest.TestCase):
def setUp(self):
+ RpclibClientTestBase.setUp(self, 'zeromq')
+
self.client = ZeroMQClient('tcp://localhost:5555', soap_application)
self.ns = "rpclib.test.interop.server._service"
28 src/rpclib/test/interop/test_suds.py
View
@@ -18,40 +18,22 @@
#
import unittest
-import time
-import urllib2
from suds.client import Client
from suds import WebFault
from datetime import datetime
+from rpclib.test.interop._test_soap_client_base import RpclibClientTestBase
+
import logging
suds_logger = logging.getLogger('suds')
suds_logger.setLevel(logging.INFO)
-_server_started = False
-
-class TestSuds(unittest.TestCase):
+class TestSuds(RpclibClientTestBase, unittest.TestCase):
def setUp(self):
- global _server_started
-
- if not _server_started:
- def run_server():
- from rpclib.test.interop.server.soap_http_basic import main
- main()
-
- import thread
- thread.start_new_thread(run_server, ())
-
- _server_started = True
-
- while True:
- try:
- self.client = Client("http://localhost:9753/?wsdl", cache=None)
- break
- except urllib2.URLError:
- time.sleep(1)
+ RpclibClientTestBase.setUp(self, 'http')
+ self.client = Client("http://localhost:9753/?wsdl", cache=None)
self.ns = "rpclib.test.interop.server._service"
def test_echo_simple_boolean_array(self):
6 src/rpclib/test/test_service.py
View
@@ -130,6 +130,7 @@ def multi(ctx, s):
class TestSingle(unittest.TestCase):
def setUp(self):
self.app = Application([TestService], 'tns', Soap11(), Soap11())
+ self.app.transport = 'null.rpclib'
self.srv = TestService()
wsdl = Wsdl11(self.app.interface)
@@ -143,14 +144,13 @@ def test_portypes(self):
len(self.srv.public_methods), len(porttype.getchildren()))
def test_override_param_names(self):
- # FIXME: This test must be rewritten.
-
for n in ['self', 'import', 'return', 'from']:
- self.assertTrue(n in self.wsdl_str, '"%s" not in self.wsdl_str' % n)
+ assert n in self.wsdl_str, '"%s" not in self.wsdl_str'
class TestMultiple(unittest.TestCase):
def setUp(self):
self.app = Application([MultipleReturnService], 'tns', Soap11(), Soap11())
+ self.app.transport = 'none'
self.wsdl = Wsdl11(self.app.interface)
self.wsdl.build_interface_document('URL')
2  src/rpclib/test/wsdl/__init__.py
View
@@ -23,7 +23,7 @@
import rpclib.const.xml_ns as ns
def build_app(service_list, tns, name):
- app = Application(service_list, tns, Wsdl11(),
+ app = Application(service_list, tns, Wsdl11(),
Soap11(), Soap11(), name=name)
app.transport = 'http://schemas.xmlsoap.org/soap/http'
return app
56 src/rpclib/test/wsdl/test_bindings.py
View
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+#encoding: utf8
#
# rpclib - Copyright (C) rpclib contributors.
#
@@ -43,24 +44,24 @@ def test_binding_simple(self):
sa.interface.build_interface_document(self.url)
services = sa.interface.root_elt.xpath(
- '/wsdl:definitions/wsdl:service',
- namespaces = {
+ '/wsdl:definitions/wsdl:service',
+ namespaces = {
'wsdl':'http://schemas.xmlsoap.org/wsdl/' })
self.assertEqual(len(services), 1)
-
+
portTypes = sa.interface.root_elt.xpath(
- '/wsdl:definitions/wsdl:portType',
- namespaces = {
+ '/wsdl:definitions/wsdl:portType',
+ namespaces = {
'wsdl':'http://schemas.xmlsoap.org/wsdl/' })
self.assertEqual(len(portTypes), 1)
ports = sa.interface.root_elt.xpath(
- '/wsdl:definitions/wsdl:service[@name="%s"]/wsdl:port' %
- "S1",
+ '/wsdl:definitions/wsdl:service[@name="%s"]/wsdl:port' %
+ "S1",
namespaces = {
'wsdl':'http://schemas.xmlsoap.org/wsdl/' })
self.assertEqual(len(ports), 1)
-
+
def test_binding_multiple(self):
sa = build_app(
@@ -70,60 +71,59 @@ def test_binding_multiple(self):
)
sa.interface.build_interface_document(self.url)
- # 2 Service,
+ # 2 Service,
# First has 1 port
# Second has 2
-
+
# => need 2 service, 3 port and 3 bindings
services = sa.interface.root_elt.xpath(
- '/wsdl:definitions/wsdl:service',
- namespaces = {
+ '/wsdl:definitions/wsdl:service',
+ namespaces = {
'wsdl':'http://schemas.xmlsoap.org/wsdl/' })
self.assertEqual(len(services), 2)
-
-
+
+
portTypes = sa.interface.root_elt.xpath(
- '/wsdl:definitions/wsdl:portType',
- namespaces = {
+ '/wsdl:definitions/wsdl:portType',
+ namespaces = {
'wsdl':'http://schemas.xmlsoap.org/wsdl/' })
self.assertEqual(len(portTypes), 3)
bindings = sa.interface.root_elt.xpath(
- '/wsdl:definitions/wsdl:binding',
+ '/wsdl:definitions/wsdl:binding',
namespaces = {
'wsdl':'http://schemas.xmlsoap.org/wsdl/' })
self.assertEqual(len(bindings), 3)
ports = sa.interface.root_elt.xpath(
- '/wsdl:definitions/wsdl:service[@name="%s"]/wsdl:port' %
- SinglePortService.__service_name__,
+ '/wsdl:definitions/wsdl:service[@name="%s"]/wsdl:port' %
+ SinglePortService.__service_name__,
namespaces = {
'wsdl':'http://schemas.xmlsoap.org/wsdl/' })
self.assertEqual(len(ports), 1)
-
+
ports = sa.interface.root_elt.xpath(
- '/wsdl:definitions/wsdl:service[@name="%s"]/wsdl:port' %
- "DoublePortService",
+ '/wsdl:definitions/wsdl:service[@name="%s"]/wsdl:port' %
+ "DoublePortService",
namespaces = {
'wsdl':'http://schemas.xmlsoap.org/wsdl/' })
self.assertEqual(len(ports), 2)
-
+
# checking name and type
#service SinglePortService
for srv in ( SinglePortService, DoublePortService ):
for port in srv.__port_types__:
bindings = sa.interface.root_elt.xpath(
'/wsdl:definitions/wsdl:binding[@name="%s"]' %
- port,
+ port,
namespaces = {
'wsdl':'http://schemas.xmlsoap.org/wsdl/' })
- self.assertEqual(bindings[0].get('type'), "tns:%s" % port)
+ self.assertEqual(bindings[0].get('type'), "tns:%s" % port)
+
+
-
-
-
58 src/rpclib/util/__init__.py
View
@@ -29,64 +29,6 @@
from urllib.parse import splithost
from urllib.parse import quote
-from rpclib.const import xml_ns as ns
-
-from lxml import etree
-
-def create_relates_to_header(relatesTo, attrs={}):
- '''Creates a 'relatesTo' header for async callbacks'''
- relatesToElement = etree.Element(
- '{%s}RelatesTo' % ns.wsa)
- for k, v in attrs.items():
- relatesToElement.set(k, v)
- relatesToElement.text = relatesTo
- return relatesToElement
-
-
-def create_callback_info_headers(message_id, reply_to):
- '''Creates MessageId and ReplyTo headers for initiating an
- async function'''
- message_id = etree.Element('{%s}MessageID' % ns.wsa)
- message_id.text = message_id
-
- reply_to = etree.Element('{%s}ReplyTo' % ns.wsa)
- address = etree.SubElement(reply_to, '{%s}Address' % ns.wsa)
- address.text = reply_to
-
- return message_id, reply_to
-
-def get_callback_info(request):
- '''
- Retrieves the messageId and replyToAddress from the message header.
- This is used for async calls.
- '''
- message_id = None
- reply_to_address = None
- header = request.soap_req_header
-
- if header:
- headers = header.getchildren()
- for header in headers:
- if header.tag.lower().endswith("messageid"):
- message_id = header.text
-
- if header.tag.lower().find("replyto") != -1:
- replyToElems = header.getchildren()
-
- for replyTo in replyToElems:
- if replyTo.tag.lower().endswith("address"):
- reply_to_address = replyTo.text
-
- return message_id, reply_to_address
-
-def get_relates_to_info(request):
- '''Retrieves the relatesTo header. This is used for callbacks'''
- header = request.soap_req_header
- if header:
- headers = header.getchildren()
- for header in headers:
- if header.tag.lower().find('relatesto') != -1:
- return header.text
def split_url(url):
'''Splits a url into (uri_scheme, host[:port], path)'''
5 src/rpclib/util/wsgi_wrapper.py
View
@@ -26,6 +26,11 @@
from rpclib.server.wsgi import WsgiApplication
class WsgiMounter(object):
+ """Simple mounter object for wsgi callables. Takes a dict where the keys are
+ uri fragments and values are :class:`rpclib.application.Application`
+ instances.
+ """
+
@staticmethod
def default(e, s):
s("404 Not found", [])
23 src/rpclib/util/xml.py
View
@@ -78,21 +78,36 @@ def get_validation_schema(models, default_namespace=None):
return interface.validation_schema
-def get_object_as_xml(value):
+
+def _dig(par):
+ for elt in par:
+ elt.tag = elt.tag.split('}')[-1]
+ _dig(elt)
+
+def get_object_as_xml(value, cls=None, root_tag_name=None, no_namespace=False):
'''Returns an ElementTree representation of a :class:`rpclib.model.complex.ComplexModel`
child.
:param value: The instance of the class to be serialized.
+ :param value: The root tag string to use. Defaults to the output of
+ ``value.__class__.get_type_name_ns()``.
'''
+ if cls is None:
+ cls = value.__class__
xml_object = XmlObject()
parent = etree.Element("parent")
- xml_object.to_parent_element(value.__class__, value, value.get_namespace(), parent)
+ xml_object.to_parent_element(cls, value, cls.get_namespace(),
+ parent, root_tag_name)
+
+ if no_namespace:
+ _dig(parent)
+ etree.cleanup_namespaces(parent)
return parent[0]
-def get_xml_as_object(cls, value):
+def get_xml_as_object(elt, cls):
'''Returns a native :class:`rpclib.model.complex.ComplexModel` child from an
ElementTree representation of the same class.
@@ -102,4 +117,4 @@ def get_xml_as_object(cls, value):
xml_object = XmlObject()
- return xml_object.from_element(cls, value)
+ return xml_object.from_element(cls, elt)
Something went wrong with that request. Please try again.