pax_global_header00006660000000000000000000000064142561140020014505gustar00rootroot0000000000000052 comment=b7a9a0e9e0ec7f3264251292ad556532904007ec suds-1.1.2/000077500000000000000000000000001425611400200124645ustar00rootroot00000000000000suds-1.1.2/.github/000077500000000000000000000000001425611400200140245ustar00rootroot00000000000000suds-1.1.2/.github/workflows/000077500000000000000000000000001425611400200160615ustar00rootroot00000000000000suds-1.1.2/.github/workflows/test_and_release.yml000066400000000000000000000032411425611400200221050ustar00rootroot00000000000000on: [push, pull_request] name: Test jobs: test: strategy: matrix: os: [ubuntu-latest] python-version: [ '3.x', '3.7', '3.8', '3.9', '3.10' ] name: Test against ${{ matrix.python-version }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} steps: - name: Checkout code uses: actions/checkout@v2 - name: Setup python uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Run the tests run: | python setup.py test release: if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') needs: [test] runs-on: ubuntu-latest name: Release steps: - name: Checkout code uses: actions/checkout@v2 - name: Setup python uses: actions/setup-python@v2 with: python-version: '3.x' - name: Install dependencies run: pip install wheel - name: Build a binary wheel and a source tarball run: python setup.py sdist bdist_wheel - name: Publish distribution 📦 to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: user: "__token__" password: ${{ secrets.PYPI_API_TOKEN_SUDS_COMMUNITY }} - name: Clean up earlier distribution run: | rm -rf dist/ - name: Build a binary wheel and a source tarball run: python setup.py sdist bdist_wheel env: SUDS_PACKAGE: suds - name: Publish distribution 📦 to PyPI (suds) uses: pypa/gh-action-pypi-publish@release/v1 with: user: "__token__" password: ${{ secrets.PYPI_API_TOKEN_SUDS }} suds-1.1.2/.gitignore000066400000000000000000000004671425611400200144630ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST suds-1.1.2/.readthedocs.yml000066400000000000000000000005601425611400200155530ustar00rootroot00000000000000# .readthedocs.yml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/source/conf.py # Optionally set the version of Python and requirements required to build your docs # python: # version: 2.7 suds-1.1.2/CHANGELOG.md000066400000000000000000002376061425611400200143130ustar00rootroot00000000000000Release notes ============= Unreleased ------------------------ version 1.1.2 (2022-06-25) ------------------------ * Restore last_sent and last_received functions version 1.1.1 (2022-03-15) ------------------------ * Use usedforsecurity=False for md5() calls to make suds work on FIPS compliant python version 1.1.0 (2022-03-15) ------------------------ * Fix undeclared variables, found via linting. * Allow subclassing Builder to always initialize optional arrays with empty lists * [ci] Drop py3.6 testing, add 3.10 * Allow subclasses of Builder to determine if children are initialized. * Don't ignore underscore prefixed attribute version 1.0.0, 1.0.0-beta.1, 1.0.0-beta.2 (2021-09-13) ------------------------ * Drop support for python 2.x version 0.8.5 (2021-05-14) ------------------------ * R4201 Any PASSWORD MUST specify a Type attribute * Compress or decompress message when Content-Encoding is present in headers * Do not force byte-compilation optimization at level 1 version 0.8.4 (2019-12-21) ------------------------ * Add per invocation timeout * Ensure request headers set in options are used to fetch definitions version 0.8.3 (2019-06-24) ------------------------ * Fix pypi description format version 0.8.2 (2019-06-23) ------------------------ * Add option to disable the sorting of namespaces. * Add option to allow unknown message parts. version 0.8.1 (2019-02-06) ------------------------ * Fix bug introduced in 0.8.0, initialize optional arrays with empty lists (@guifran001) version 0.8.0 (2019-01-31) ------------------------ * [Breaking] Objects should not be instantiated with empty optional attributs, see https://github.com/suds-community/suds/issues/14 for more information. (@guifran001) * Add WSSE password digest and nonce encoding type (@ovnicraft) version 0.7.3 (2019-01-04) ------------------------ * Pass header while requesting a WSDL (@guifran001) version 0.7.2 (2018-10-25) ------------------------ * Fix Travis deploy (@btb) version 0.7.1 (2018-10-15) ------------------------ * Documentation updates (@phillbaker) * Add travis deploy (@phillbaker) version 0.7.0 (2018-09-29) ------------------------ - Based on revision 712 (1e48fd79a1fc323006826439e469ba7b3d2b5a68) from the original `suds` Python library development project\'s Subversion repository. - Last officially packaged & released `suds` Python library version - 0.4.1. - Supported Python versions. - Intended to work with Python 2.4+, except for the following versions: - Python 3.0.x - not supported by setuptools, pip or pytest. - Basic sources prepared for Python 2.x. - For using Python 3 the sources first processed by the Python `py2to3` tool during the setup procedure. - Tested in the environments listed in the [.travis.yml]{.title-ref} - Improved support for `decimal` XSD types. - Now modeled internally using Python\'s `decimal.Decimal` type instead of `float` - see the new `suds.xsd.sxbuiltin.XDecimal` class. - Based on a patch included with [#454](http://fedorahosted.org/suds/ticket/454) for the original `suds` library implementation. - In order to get a `decimal` value formatted correctly in constructed SOAP request XML documents, pass it to `suds` as `decimal.Decimal` or an `int`/`long`. - In general, passing a value of a Python type other than `decimal.Decimal` causes that type\'s native string representation to be used which might not strictly match the lexical representation rules defined in the XSD specification for the `decimal` XSD type. For instance, a `float` value may be represented using scientific notation, or a `fractions.Fraction` may be represented using its `numerator` & `denominator` values. - Specific user applications can easily register their own customized `XDecimal` implementation using `suds.xsd.sxbuiltin.Factory.maptag()` if they want to use more specialized `decimal` value handling. - Updated how `suds` constructs its cached WSDL & XML identifiers to allow cached data reuse between different processes with Python\'s hash randomization feature enabled. - Previously constructed using the built-in Python `hash()` function, while now it gets constructed using `md5` hash. - Python\'s hash randomization (implemented since Python 2.6.8, enabled by default since Python 3.3) was causing different processes to mangle their cached data names differently. - Many thanks to Eugene Yakubovich for reporting the issue as well as providing the initial fix. - Fixed loading recursive WSDL imports. - Fixed loading recursive XSD imports/includes. - Fixed an infinite recursion bug encountered when looking for an XSD type in an XSD schema that does not contain it but itself defines a type referencing a recursively defined type from an imported XSD schema. - Kudos to Kevin Pors ([krpors]{.title-ref} on BitBucket) for detecting, analysing & reporting the issue, plus preparing a working quick-fix. - Removed never actually used `suds.mx.appender.DictAppender` class. - All code paths that could potentially lead to this class getting used convert any encountered dictionaries to `suds.sudsobject.Object` instances and report an error in case a corresponding XSD type can not be found. - Now marshalling passed empty object optional params as empty SOAP request XML elements. - Before, passing an empty suds object as an optional parameter value was treated the same as not passing that parameter\'s value or passing it `None` - the value would not get marshalled into the constructed SOAP request at all. - Now, user can still not have the value marshalled by passing nothing or `None`, but passing an empty object will get marshalled as an actual SOAP request XML element. - Kudos to Nicholas Chen (nicholaschen at BitBucket) & Mark Saniscalchi (msaniscalchi at BitBucket) for reporting the issue and preparing the initial fix. - Made `suds` no longer eat up, log & ignore exceptions raised from registered user plugins (detected & reported by Ezequiel Ruiz & Bouke Haarsma, patch & test case contributed by Bouke Haarsma). - Fixed places in code where `suds` could eat up & silently ignore internal Python exceptions like `KeyboardInterrupt` or `SystemExit`. - Fixed the exception message used when attempting to construct a `suds.sax.element.Element` with a non-`Element` parent. - `suds.xsd.sxbase.SchemaObject.content()` now runs in linear instead of quadratic time. - `DepList` class replaced with a simple `dependency_sort()` function taking a single dependency dictionary as input. - The original implementation\'s interface was too heavy-weight with no added value. - Anything tried with the original interface outside the basic use-case covered by `dependency_sort()` was actually or could be easily broken. - `suds.xsd.deplist` module renamed to `suds.xsd.depsort`. - Global XSD elements (i.e. top-level + reference elements) are now correctly always considered qualified and their `form` attribute values are ignored. - Many thanks to Andrew Yager from BitBucket for reporting the issue. - `suds.cache` module cleanup. - Fixed `FileCache` default cache location related security issue. - Each process now uses a separate temporary folder as its default cache location. - Different `FileCache` instances within the same process still use the same default cache location and user may still explicitly specify a non-default location for each `FileCache` instance. - Default cache location now gets removed automatically on process exit. User code may disable this removal by setting the `FileCache.remove_default_location_on_exit` class attribute to False. - Additional external information on this issue: - [Red Hat bug 978696](https://bugzilla.redhat.com/show_bug.cgi?id=978696) - [CVE-2013-2217](http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-2217) - [Ubuntu USN-2008-1: Suds vulnerability](http://www.ubuntu.com/usn/USN-2008-1) - - - - Many thanks to Rolf Krahl for the initial report, providing links to related external resources as well as helping brainstorm the whole issue. - Fixed a bug causing `DocumentCache` to never actually cache any documents since one of the last commits made to the original `suds` project. - That commit refactored `suds.sax.document.Document` so it is no longer derived from `suds.sax.element.Element` while the `suds.cache.DocumentCache.put()` implementation simply did nothing when passed something other than a `suds.sax.element.Element` instance. `suds.reader.DocumentReader` on the other hand always passes `suds.sax.document.Document` instances to its cache\'s `put()` method. - Many thanks to bgr\_ at BitBucket for reporting the issue. - Fixed a bug causing `DocumentCache` & `ObjectCache` to not remove their cached files when failing to read data from them or process the data read from them. - `FileCache` version file operations now take care to close the file in case of a failed read/write operation. - Removed `FileCache.setlocation()` method as it was never used inside `suds` and if used from user code would have caused the cache to use a specific folder but without making sure that the data already stored in it has been prepared for the correct `suds` version, as done when passing a location parameter to the `FileCache` constructor. - Private and protected `FileCache` interface methods renamed to use leading underscores. - `FileCache.getf()` \--\> `FileCache._getf()`. - `FileCache.__fn()` \--\> `FileCache.__filename()`. - `FileCache.checkversion()` \--\> `FileCache.__check_version()`. - `FileCache.mktmp()` \--\> `FileCache.__mktmp()`. - `FileCache.open()` \--\> `FileCache.__open()`. - `FileCache.setduration()` \--\> `FileCache.__set_duration()`. - `FileCache.validate()` \--\> `FileCache.__remove_if_expired()`. - Updated `FileCache` duration implementation. - `FileCache` construction now takes standard `datetime.timedelta` duration related keyword arguments instead of just `weeks`, `days`, `hours`, `minutes` & `seconds`. More to the point, it now also supports `milliseconds` & `microseconds` keyword arguments. - Corrected `FileCache` docstring stating that it accepted a `months` keyword argument. Using that argument would actually have caused a failure when passing it to a `datetime.timedelta` initializer internally. - You may now specify multiple duration keyword arguments in `FileCache` construction and they will all get summed up when constructing the internal `datetime.timedelta` duration representation. Before, you could specify such multiple arguments, but that would only make the `FileCache` silently use duration `0`, i.e. its cache entries would never expire. - Fixed `suds.sax.document.Document` str conversion broken around the end of 2011 by some accidental interaction between our Python 3 compatibility fixes and one of the final official `suds` project commits making `suds.sax.document.Document` no longer be derived from `suds.sax.element.Element`. - Many thanks to Ezequiel Ruiz (emruiz81 at BitBucket) for detecting & reporting the issue, as well as providing the initial patch. - Cleaned up `suds.transport` ASCII/unicode URL/data handling. - `suds.transport.Request` now allows specifying its URL input as either a byte or a unicode string with any Python version. Internally that URL information is always converted to the used Python interpreter\'s native `str` data type (byte string for Python versions prior to 3.0, or unicode string for later ones). - Given URLs must not contain any non-ASCII characters, and any attempt to create a `suds.transport.Request` with such an invalid URL is reported as a `UnicodeError` (either `UnicodeDecodeError` or `UnicodeEncodeError` depending on the exact Python version and the given URL data type used). - `suds.transport.Reply` & `suds.transport.Request` string representation cleaned up and no longer raises an error when their message data contains non-ASCII characters. - `suds.client` module cleanup. - Removed unused `suds.client.Client.messages` attribute. - Renamed private `SoapClient` & `SimClient` classes: - `SoapClient` \--\> `_SoapClient`. - `SimClient` \--\> `_SimClient`. - Several private methods renamed: - `_SoapClient.location()` \--\> `_SoapClient.__location()`. - `_SoapClient.get_fault()` \--\> `_SoapClient.__get_fault()`. - `_SoapClient.headers()` \--\> `_SoapClient.__headers()`. - `RequestContext` no longer has `client` & `original_envelope` attributes. - `client` attribute seems unnecessary. - `original_envelope` was an incorrectly documented bug trap - it represented the XML request envelope as a `SAX` XML document from after being processed by registered `marshalled` plugins, but before being processed by registered `sending` plugins. Users should use the `envelope` attribute instead which can easily be converted into a `SAX` XML document if needed by parsing it using `suds.sax.parser.Parser.parse()`. That envelope has been consistently processed by all relevant registered plugins and matches the data to be sent over the registered transport exactly. - Cleaned up `_SoapClient` debug log messages a bit. - `suds.reader` module cleanup. - Several private methods renamed: - `DocumentReader.cache()` \--\> `DocumentReader.__cache()` - `DocumentReader.download()` \--\> `DocumentReader.__fetch()` - `DefinitionsReader.cache()` \--\> `DefinitionsReader.__cache()` - Updated the `BuildError` exception message. - Reformatted. - Converted to a unicode string. - Marked `suds.mx.core.Core.node()` as abstract since this base class variant is never actually used (both `Encoded` & `Literal` derived classes use a different implmentation). - `suds.binding.Binding` converted to a new-style class. - `suds.tostr()` utility function may no longer silently eat internal Python exceptions like `KeyboardInterrupt` or `SystemExit`. - Removed the unused `SoapHeadersNotPermitted` exception class. - Extra input arguments now reported when invoking web service operations taking no input parameters. - Using injected requests/replies/error-information with a web service operation taking at least one input parameter no longer causes `suds` to report an invalid extra argument error. - Internal project development improvements. - The project will from now on be distributed as a wheel as well as a source distribution. - Added a script for automatically setting up required development Python environments for this project, hopefully supporting the full range of supported Python versions out of the box. - Improved internal project `HACKING.rst` documentation. - `setup.py` improvements. - Python 3.0.x releases explicitly marked as not supported. - Attempting to run `setup.py` in an unsupported Python environment now reports a clean error message. - Now uses `setuptools` 1.4.2 with Python 2.4 & 2.5, and `setuptools` 5.1 with all more recent Python releases. - Project may now be installed without even in environments when you can not install `setuptools`. - In such cases `setup.py` will attempt to use any preinstalled `setuptools` version, and if none is available, it will disable some of its features and fall back to using a plain `distutils` based setup. See the `setup.py` script comments for a more detailed listing of all `setup.py` features affected by this. - Several installation issues fixes when installing into Python 3.x environments prior to Python 3.2.3. - When installing the project into a Python 3.x environment prior to Python 3.2, `setuptools` is not installed automatically since one of its test modules contains UTF-8 BOM characters, which would cause such automated installation to fail. - If needed, `setuptools` can still be installed into such environments by manually running its `ez_setup.py` installation script. Such an installation will encounter the same errors but will ignore them, effectively just leaving the installed `setuptools` package with one defective test module, but fully operational at run-time. - When installing the project into a Window Python 2.5 environment, you no longer need to manually install a compatible `colorama` package versions in order to be able to run the project tests. - Package meta-data may now contain non-ASCII characters on platforms where that is allowed, namely with all Python versions except Python 3.x prior to 3.2.2. - `setup.py test` command improvements. - Now works in Python 2.4.x environments. - Now reports cleanly if it can not be used for some reason, both when run and in the command\'s `--help-commands` listing. - Better commented the related implementation. - Test suite improvements. - Test suite no longer installed together with the project, thus no longer causing confusion by existing in the target Python environment as a global `tests` package. - The tests may now be run from the source archive, and will always run on the `suds` version found installed in the used Python environment. - Refactored the quick & dirty batch script used to run all the project tests in multiple Python environments to remove much code duplication. - Automated project testing in several additional Python environment versions. - Added more detailed XSD modeling tests. - Added tests demonstrating how additional or replacement built-in XSD types can be registered with `suds`. - All project tests now using Python 2 & 3 compatible source code and so no longer need to be built separately for Python 3. - Added new and updated existing `suds.cache` module related tests. - Documented that all `pytest` test parametrizations should be prepared so they get ordered the same on all test runs. See `Project implementation note #1` in `HACKING.rst` for more detailed information. - Many thanks to Bruno Oliveira (nicoddemus at BitBucket) for researching related `pytest` `xdist` usage problems, discovering & explaining the underlying issue as well as providing an initial project patch for it. version 0.6 (2014-01-24) ------------------------ - Based on revision 712 (1e48fd79a1fc323006826439e469ba7b3d2b5a68) from the original `suds` Python library development project\'s Subversion repository. - Last officially packaged & released `suds` Python library version - 0.4.1. - Supported Python versions. - Intended to work with Python 2.4+. - Basic sources prepared for Python 2.x. - For using Python 3 the sources first processed by the Python `py2to3` tool during the setup procedure. - Tested in the following environments: - Python 2.4.3/x86, on Windows 7/SP1/x64. - Python 2.4.4/x86, on Windows 7/SP1/x64. - Python 2.7.6/x64, on Windows 7/SP1/x64. - Python 3.2.5/x64, on Windows 7/SP1/x64. - Python 3.3.3/x86, on Windows 7/SP1/x64. - Python 3.3.3/x64, on Windows 7/SP1/x64. - Fixed sending HTTP request containing non-ASCII unicode data using Python 2.7. - Many thanks to mduggan1 and Alexey Sveshnikov for reporting the issue and suggesting patches. - Fixed unicode data logging issue (contributed by Bouke Haarsma). - `suds.transport.Request` object string representation cleaned up a bit -added a missing space before the URL to make it consistent with how all the other Request & Reply data is represented in such strings. - Fixed issue with `suds` client failing to be create its default cache object (e.g. because a folder it needs is write protected) and thus preventing the client from being created without any chance for the user to specify an alternative cache. - The default client cache is now instantiated only if user does not explicitly specify some other alternate cache (or even None to disable the whole data caching system). - Many thanks to Arthur Clune for reporting the issue. - Added explicit tests for URL parameters being passed as unicode or single-byte strings under Python 2 but only unicode strings under Python 3, and improved how such invalid parameter values are reported. - This behaviour matches urllib implementation differences between Python 3 and earlier Python interpreter versions. - Many thanks to Mesut Tasci for reporting a related issue and preparing the initial patch for it. - Extra arguments used when making a web service operation call are now reported similar to how this is done for regular Python functions. - The extra argument error reporting may be disabled using the new `extraArgumentErrors` `suds` option. - Basic idea and the initial implementation for this feature contributed by Bouke Haarsma. - Corrected a typo in the `BuildError` exception message. - Removed partial support for pre-2.4 Python versions since such old Python versions are no longer officially supported nor are they tested anywhere. - Updated documented project links to use HTTP instead of HTTPS protocol. - Setup improvements. - Fixed setup to work with soft links in the current working folder path (contributed by ryanpetrello). - Project now installed as a zipped egg folder. - No longer attempts to work around Python 2.4.3 issues with urllib HTTPS downloads since now PyPI updated all of its links to HTTPS and the patch would need to become much more complex to deal with this, while making the setup much more difficult to understand and maintain. - On the other hand, this is now an extremely old Python version, so the change is not expected to have much impact. Anyone still using this version will just have to work around the issue manually, e.g. by downloading the necessary packages and running their setup procedures directly. - `long_description` field content wrapped to 72 characters, since `PKG-INFO` package distribution metadata file stores this text with an 8 space indentation. - Improved internal project development documentation. - `HACKING.txt` updated, converted to .rst format & renamed to `HACKING.rst`. - Started internal project design, research & development notes documentation. Stored in a new `notes/` subfolder, included in the project\'s source distribution, but not its builds or installations. - Internal test suite improvements. - Added unit tests for transport related `Request` & `Reply` classes. - Improved `HTTPTransport` related unit tests. - Split up some web service operation invocation request construction tests into: - parameter definition tests - argument parsing tests - binding specific request construction tests - Many new tests added & existing ones extended. - Several redundant tests removed. - Added a basic development script for running the project\'s full test suite using multiple Python interpreter versions under Windows. - Better test support when running with disabled assertion optimizations enabled. - Cleaned up support for running test scripts directly as Python scripts. - May now be passed pytest command-line options. - Now return an exit code indicating the test result (0=success, !0=failure). - Known defects. - Extra argument errors not reported for web service operations taking no input parameters. - Invalid extra argument error reported when using an injected request/reply/ error-information with a web service operation taking at least one input parameter. - Security issue CVE-2013-2217 - using fixed default cache location. - Incorrect referencing XSD element\'s `form` attribute value handling -global XSD elements (i.e. top-level + reference elements) sometimes considered unqualified. - Loading recursive WSDL imports is broken. - Loading recursive XSD imports/includes is broken. - Infinite recursion bug encountered when looking for an XSD type in an XSD schema that does not contain it but itself defines a type referencing a recursively defined type from an imported XSD schema. version 0.5 (2013-11-25) ------------------------ - Based on revision 712 (1e48fd79a1fc323006826439e469ba7b3d2b5a68) from the original `suds` Python library development project\'s Subversion repository. - Last officially packaged & released `suds` Python library version - 0.4.1. - Supported Python versions. - Intended to work with Python 2.4+. - Basic sources prepared for Python 2.x. - For using Python 3 the sources first processed by the Python `py2to3` tool during the setup procedure. - Tested in the following environments: - Python 2.4.3/x86, on Windows 7/SP1/x64. - Python 2.4.4/x86, on Windows 7/SP1/x64. - Python 2.7.6/x64, on Windows 7/SP1/x64. - Python 3.2.5/x64, on Windows 7/SP1/x64. - Python 3.3.3/x86, on Windows 7/SP1/x64. - Python 3.3.3/x64, on Windows 7/SP1/x64. - Updated the project\'s versioning scheme and detached it from the original `suds` project. The original project\'s stall seems to be long-term (likely permanent) and making our version information match the original one was getting to be too much of a hassle. - For example, with our original versioning scheme, latest pip versions recognize our package releases as \'development builds\' and refuse to install them by default (supply the `--pre` command-line option to force the install anyway). - Improved the `suds` date/time handling (contributed by MDuggan1, based on a patch attached to issue [#353](http://fedorahosted.org/suds/ticket/353) on the original `suds` project issue tracker). - Replaces the timezone handling related fix made in the previous release. - More detailed testing. - Corrected subsecond to microsecond conversion, including rounding. - `DateTime` class no longer derived from `Date` & `Time` classes. - Recognizes more date/time strings valid \'by intuition\'. - Rejects more invalid date/time strings. - Time zone specifiers containing hours and minutes but without a colon are rejected to avoid confusion, e.g. whether `+121` should be interpreted as `+12:01` or `+01:21`. - Time zone specifiers limited to under 24 hours. Without this Python\'s timezone UTC offset calculation would raise an exception on some operations, e.g. timezone aware `datetime.datetime`/`time` comparisons. - Removed several project files related to the original developer\'s development environment. - Removed several internal Mercurial version control system related files from the project\'s source distribution package. - Better documented the project\'s development & testing environment. - Known defects. - Security issue CVE-2013-2217 - using fixed default cache location. - Incorrect referencing XSD element\'s `form` attribute value handling -global XSD elements (i.e. top-level + reference elements) sometimes considered unqualified. - Loading recursive WSDL imports is broken. - Loading recursive XSD imports/includes is broken. - Infinite recursion bug encountered when looking for an XSD type in an XSD schema that does not contain it but itself defines a type referencing a recursively defined type from an imported XSD schema. version 0.4.1 jurko 5 (2013-11-11) ---------------------------------- - Based on revision 712 (1e48fd79a1fc323006826439e469ba7b3d2b5a68) from the original `suds` Python library development project\'s Subversion repository. - Last officially packaged & released `suds` Python library version - 0.4.1. - Supported Python versions. - Intended to work with Python 2.4+. - Basic sources prepared for Python 2.x. - For using Python 3 the sources first processed by the Python `py2to3` tool during the setup procedure. - Tested in the following environments: - Python 2.4.3/x86, on Windows 7/SP1/x64. - Python 2.4.4/x86, on Windows 7/SP1/x64. - Python 2.7.3/x64, on Windows 7/SP1/x64. - Python 3.2.3/x64, on Windows 7/SP1/x64. - Python 3.3.2/x86, on Windows 7/SP1/x64. - Python 3.3.2/x64, on Windows 7/SP1/x64. - Improved Python 3 support. - Cache files now used again. - Problems caused by cache files being stored in text mode, but attempting to write a bytes object in them. Too eager error handling was then causing all such cached file usage to fail silently. - `WebFault` containing non-ASCII data now gets constructed correctly. - Fixed issue with encoding of authentication in `transport/http.py` (contributed by Phillip Alday). - Unicode/byte string handling fixes. - Fixed encoding long user credentials for basic HTTP authentication in `transport/http.py` (contributed by Jan-Wijbrand Kolman). - Fixed an `IndexError` occurring when calling a web service operation with only a single input parameter. - Fixed a log formatting error, originated in the original `suds` (contributed by Guy Rozendorn). - Fixed local timezone detection code (contributed by Tim Savage). - Setup updated. - Fixed a problem with running the project setup on non-Windows platforms. - `version.py` file loading no longer sensitive to the line-ending type used in that file. - Stopped using the `distribute` setup package since it has been merged back into the original `setuptools` project. Now using `setuptools` version 0.7.2 or later. - Automatically downloads & installs an appropriate `setuptools` package version if needed. - `distutils` `obsoletes` setup parameter usage removed when run using this Python versions earlier than 2.5 as that is the first version implementing support for this parameter. - Removed different programming techniques & calls breaking compatibility with Python 2.4. - String `format()` method. - Ternary if operator. - Project `README` file converted to .rst format (contributed by Phillip Alday). - Corrected internal input/output binding usage. Output binding was being used in several places where the input one was expected. - HTTP status code 200 XML replies containing a `Fault` element now consistently as a SOAP fault (plus a warning about the non-standard HTTP status code) both when reporting such faults using exceptions or by returning a (status, reason) tuple. - Before this was done only when reporting them using exceptions. - Reply XML processing now checks the namespace used for `Envelope` & `Body` elements. - SOAP fault processing now checks the namespaces used for all relevant tags. - Plugins now get a chance to process `received()` & `parsed()` calls for both success & error replies. - SOAP fault reports with invalid Fault structure no longer cause `suds` code to break with an \'invalid attribute\' exception. - SOAP fault reports with no `` tag (optional) no longer cause `suds` code to break with an \'invalid attribute\' exception when run with the `suds` `faults` option set to `False`. - Added correct handling for HTTP errors containing no input file information. Previously such cases caused `suds` to break with an \'invalid attribute\' exception. - `SimClient` injection keywords reorganized: - `msg` - request message. - `reply` - reply message (\'msg\' must not be set). - `status` - HTTP status code accompanying the \'reply\' message. - `description` - description string accompanying the \'reply\' message. - Added `unwrap` option, allowing the user to disable `suds` library\'s automated simple document interface unwrapping (contributed by Juraj Ivančić). - SOAP request parameter XML elements no longer constructed in incorrect namespaces in case they have been defined by XSD schema elements referencing XSD schema elements with a different target namespace. - `DocumentStore` instance updated. - Separate `DocumentStore` instances now hold separate data with every instance holding all the hardcoded `suds` library XML document data. - `DocumentStore` now supports a dict-like `update()` method for adding new documents to it. - `Client` instances may now be given a specific `DocumentStore` instance using the \'documentStore\' option. Not specifying the option uses a shared singleton instance. Specifying the option as `None` avoids using any document store whatsoever. - `suds` tests no longer have to modify the global shared `DocumentStore` data in order to avoid loading its known data from external files and so may no longer affect each other by leaving behind data in that global shared `DocumentStore`. - Documents may now be fetched from a `DocumentStore` using a transport protocol other than `suds`. When using the `suds` protocol an exception is raised if the document could not be found in the store while in all other cases `None` is returned instead. - Documents in a `DocumentStore` are now accessed as bytes instead file-like stream objects. - Made more `DocumentStore` functions private. - Corrected error message displayed in case of a transport error. - Many unit tests updated and added. - Unit tests may now be run using the setuptools `setup.py test` command. - Note that this method does not allow passing additional pytest testing framework command-line arguments. To specify any such parameters invoke the pytest framework directly, e.g. using `python -m pytest` in the project\'s root folder. - Internal code cleanup. - Removed undocumented, unused and untested `binding.replyfilter` functionality. - Binding classes no longer have anything to do with method independent Fault element processing. - Removed SoapClient `last_sent()` and `last_received()` functions. - Fixed file closing in `reader.py` & `cache.py` modules - used files now closed explicitly in case of failed file operations instead of relying on the Python GC to close them \'some time later on\'. - Fixed silently ignoring internal exceptions like `KeyboardInterrupt` in the `cache.py` module. - Removed unused `Cache` module `getf()` & `putf()` functions. `getf()` left only in `FileCache` and its derived classes. - Known defects. - Security issue CVE-2013-2217 - using fixed default cache location. - Incorrect referencing XSD element\'s `form` attribute value handling -global XSD elements (i.e. top-level + reference elements) sometimes considered unqualified. - Loading recursive WSDL imports is broken. - Loading recursive XSD imports/includes is broken. - Infinite recursion bug encountered when looking for an XSD type in an XSD schema that does not contain it but itself defines a type referencing a recursively defined type from an imported XSD schema. version 0.4.1 jurko 4 (2012-04-17) ---------------------------------- - Based on revision 712 (1e48fd79a1fc323006826439e469ba7b3d2b5a68) from the original `suds` Python library development project\'s Subversion repository. - Last officially packaged & released `suds` Python library version - 0.4.1. - Supported Python versions. - Intended to work with Python 2.4+. - Basic sources prepared for Python 2.x. - For using Python 3 the sources first processed by the Python `py2to3` tool during the setup procedure. - Installation procedure requires the `distribute` Python package to be installed on the system. - Tested in the following environments: - Python 2.7.1/x64 on Windows XP/SP3/x64. - Python 3.2.2/x64 on Windows XP/SP3/x64. - Cleaned up how the distribution package maintainer name string is specified so it does not contain characters causing the setup procedure to fail when run using Python 3+ on systems using CP1250 or UTF-8 as their default code-page. - Internal cleanup - renamed bounded to single\_occurrence and unbounded to multi\_occurrence. - Original term unbounded meant that its object has more than one occurrence while its name inferred that \'it has no upper limit on its number of occurrences\'. - Known defects. - SOAP request parameter XML elements constructed in incorrect namespaces in case they have been defined by XSD schema elements referencing XSD schema elements with a different target namespace. - Security issue CVE-2013-2217 - using fixed default cache location. - Incorrect referencing XSD element\'s `form` attribute value handling -global XSD elements (i.e. top-level + reference elements) sometimes considered unqualified. - Loading recursive WSDL imports is broken. - Loading recursive XSD imports/includes is broken. - Infinite recursion bug encountered when looking for an XSD type in an XSD schema that does not contain it but itself defines a type referencing a recursively defined type from an imported XSD schema. version 0.4.1 jurko 3 (2011-12-26) ---------------------------------- - Based on revision 711 (1be817c8a7672b001eb9e5cce8842ebd0bf424ee) from the original `suds` Python library development project\'s Subversion repository. - Last officially packaged & released `suds` Python library version - 0.4.1. - Supported Python versions. - Intended to work with Python 2.4+. - Basic sources prepared for Python 2.x. - For using Python 3 the sources first processed by the Python `py2to3` tool during the setup procedure. - Installation procedure requires the `distribute` Python package to be installed on the system. - Tested in the following environments: - Python 2.7.1/x86 on Windows XP/SP3/x86. - Python 3.2.2/x86 on Windows XP/SP3/x86. - Operation parameter specification string no longer includes a trailing comma. - `suds.xsd.xsbasic.Enumeration` objects now list their value in their string representation. - `suds.sudsobject.Metadata` `__unicode__()`/`__str__()`/`__repr__()` functions no longer raise an `AttributeError` when the object is not empty. - Fixed a bug with `suds.xsd.sxbasic.TypedContent.resolve()` returning an incorrect type when called twice on the same node referencing a builtin type with the parameter `nobuiltin=True`. - Added more test cases. - Known defects. - SOAP request parameter XML elements constructed in incorrect namespaces in case they have been defined by XSD schema elements referencing XSD schema elements with a different target namespace. - Security issue CVE-2013-2217 - using fixed default cache location. - Incorrect referencing XSD element\'s `form` attribute value handling -global XSD elements (i.e. top-level + reference elements) sometimes considered unqualified. - Loading recursive WSDL imports is broken. - Loading recursive XSD imports/includes is broken. - Infinite recursion bug encountered when looking for an XSD type in an XSD schema that does not contain it but itself defines a type referencing a recursively defined type from an imported XSD schema. version 0.4.1 jurko 2 (2011-12-24) ---------------------------------- - Based on revision 711 (1be817c8a7672b001eb9e5cce8842ebd0bf424ee) from the original `suds` Python library development project\'s Subversion repository. - Last officially packaged & released `suds` Python library version - 0.4.1. - Supported Python versions. - Intended to work with Python 2.4+. - Basic sources prepared for Python 2.x. - For using Python 3 the sources first processed by the Python `py2to3` tool during the setup procedure. - Installation procedure requires the `distribute` Python package to be installed on the system. - Tested in the following environments: - Python 2.7.1/x86 on Windows XP/SP3/x86. - Python 3.2.2/x86 on Windows XP/SP3/x86. - Fixed a bug causing converting a `suds.client.Client` object to a string to fail & raise an `IndexError` exception. - Changed the way `suds.client.Client to-string` conversion outputs build info. This fixes a bug in the original `0.4.1 jurko 1` forked project release causing printing out a `suds.client.Client` object to raise an exception due to the code in question making some undocumented assumptions on how the build information string should be formatted. - Known defects. - SOAP request parameter XML elements constructed in incorrect namespaces in case they have been defined by XSD schema elements referencing XSD schema elements with a different target namespace. - Security issue CVE-2013-2217 - using fixed default cache location. - Incorrect referencing XSD element\'s `form` attribute value handling -global XSD elements (i.e. top-level + reference elements) sometimes considered unqualified. - Loading recursive WSDL imports is broken. - Loading recursive XSD imports/includes is broken. - Infinite recursion bug encountered when looking for an XSD type in an XSD schema that does not contain it but itself defines a type referencing a recursively defined type from an imported XSD schema. version 0.4.1 jurko 1 (2011-12-24) ---------------------------------- - Based on revision 711 (1be817c8a7672b001eb9e5cce8842ebd0bf424ee) from the original `suds` Python library development project\'s Subversion repository. - Last officially packaged & released `suds` Python library version - 0.4.1. - Supported Python versions. - Intended to work with Python 2.4+. - Basic sources prepared for Python 2.x. - For using Python 3 the sources first processed by the Python `py2to3` tool during the setup procedure. - Installation procedure requires the `distribute` Python package to be installed on the system. - Tested in the following environments: - Python 2.7.1/x86 on Windows XP/SP3/x86. - Python 3.2.2/x86 on Windows XP/SP3/x86. - Added Python 3 support: - Based on patches integrated from a Mercurial patch queue maintained by [Bernhard Leiner](https://bitbucket.org/bernh/suds-python-3-patches). - Last collected patch series commit: `96ffba978d5c74df28846b4273252cf1f94f7c78`. - Original sources compatible with Python 2. Automated conversion to Python 3 sources during setup. - Automated conversion implemented by depending on the `distribute` setup package. - Made `suds` work with operations taking choice parameters. - Based on a patch by michaelgruenewald & bennetb01 attached to ticket [#342](http://fedorahosted.org/suds/ticket/342) on the original `suds` project issue tracker. Comments listed related to that ticket seem to indicate that there may be additional problems with this patch but so far we have not encountered any. - Fixed the `DateTimeTest.testOverflow` test to work correctly in all timezones. - This test would fail if run directly when run on a computer with a positive timezone time adjustment while it would not fail when run together with all the other tests in this module since some other test would leave behind a nonpositive timezone adjustment setting. Now the test explicitly sets its own timezone time adjustment to a negative value. - Fixes a bug referenced in the original `suds` project issue tracker as ticket [#422](http://fedorahosted.org/suds/ticket/422). - Corrected accessing `suds.xsd.sxbase.SchemaObject` subitems by index. - Fixes a bug referenced in the original `suds` project issue tracker as ticket [#420](http://fedorahosted.org/suds/ticket/420). - Internal code & project data cleanup. - Extracted version information into a separate module. - Added missing release notes for the original `suds` Python library project. - Ported unit tests to the `pytest` testing framework. - Cleaned up project tests. - Separated standalone tests from those requiring an external web service. - Added additional unit tests. - Added development related documentation - `HACKING.txt`. - Setup procedure cleaned up a bit. - Known defects. - Converting a `suds.client.Client` object to a string fails & raises an `IndexError` exception. - SOAP request parameter XML elements constructed in incorrect namespaces in case they have been defined by XSD schema elements referencing XSD schema elements with a different target namespace. - Security issue CVE-2013-2217 - using fixed default cache location. - Incorrect referencing XSD element\'s `form` attribute value handling -global XSD elements (i.e. top-level + reference elements) sometimes considered unqualified. - Loading recursive WSDL imports is broken. - Loading recursive XSD imports/includes is broken. - Infinite recursion bug encountered when looking for an XSD type in an XSD schema that does not contain it but itself defines a type referencing a recursively defined type from an imported XSD schema. Original suds library release notes =================================== **version 0.4.1 (2010-10-15)** - \ - Known defects. - SOAP request parameter XML elements constructed in incorrect namespaces in case they have been defined by XSD schema elements referencing XSD schema elements with a different target namespace. - Security issue CVE-2013-2217 - using fixed default cache location. - Incorrect referencing XSD element\'s `form` attribute value handling -global XSD elements (i.e. top-level + reference elements) sometimes considered unqualified. - Loading recursive WSDL imports is broken. - Loading recursive XSD imports/includes is broken. - Infinite recursion bug encountered when looking for an XSD type in an XSD schema that does not contain it but itself defines a type referencing a recursively defined type from an imported XSD schema. **version 0.4 (2010-09-08)** - Fix spelling errors in spec description. - Fix source0 URL warning. - Updated caching to not cache intermediate WSDLs. - Added `DocumentCache` which caches verified XML documents as text. User can choose. - Added `cachingpolicy` option to allow user to specify whether to cache XML documents or WSDL objects. - Provided for repeating values in reply for message parts consistent with the way this is handled in nested objects. - Added `charset=utf-8` to stock content-type HTTP header. - Added `` to outgoing SOAP messages. - Detection of faults in successful (http=200) replies and raise `WebFault`. Search for ``. - Add plugins facility. - Fixed Tickets: #251, #313, #314, #334. **version 0.3.9 (2009-12-17)** - Bumped python requires to 2.4. - Replaced stream-based caching in the transport package with document-based caching. - Caches pickled `Document` objects instead of XML text. 2x Faster! - No more SAX parsing exceptions on damaged or incomplete cached files. - Cached WSDL objects. Entire `Definitions` object including contained `Schema` object cached via pickle. - Copy of SOAP encoding schema packaged with `suds`. - Refactor `Transports` to use `ProxyHandler` instead of `urllib2.Request.set_proxy()`. - Added WSSE enhancements `` and `` support. See: Timestamp token. - Fixed Tickets: #256, #291, #294, #295, #296. **version 0.3.8 (2009-12-09)** - Included Windows NTLM Transport. - Add missing `self.messages` in `Client.clone()`. - Changed default behavior for WSDL `PartElement` to be optional. - Add support for services/ports defined without `
` element in WSDL. - Fix `sax.attribute.Element.attrib()` to find by name only when ns is not specified; renamed to `Element.getAttribute()`. - Update `HttpTransport` to pass timeout parameter to urllib2 open() methods when supported by urllib2. - Add `null` class to pass explicit NULL values for parameters and optional elements. - SOAP encoded array `soap-enc:Array` enhancement for rpc/encoded. Arrays passed as python arrays - works like document/literal now. No more using the factory to create the Array. Automatically includes `arrayType` attribute. E.g. `soap-enc:arrayType="Array[2]"`. - Reintroduced ability to pass complex (objects) using python dict instead of `suds` object via factory. - Fixed tickets: #84, #261, #262, #263, #265, #266, #278, #280, #282. **version 0.3.7 (2009-10-16)** - Better SOAP header support - Added new transport `HttpAuthenticated` for active (not passive) basic authentication. - New options (`prefixes`, `timeout`, `retxml`). - WSDL processing enhancements. - Expanded builtin XSD type support. - Fixed ``. - Better XML `date`/`datetime` conversion. - `Client.clone()` method added for lightweight copy of client object. - XSD processing fixes/enhancements. - Better `` by `` support. - Performance enhancements. - Fixed tickets: #65, #232, #233, #235, #241, #242, #244, #247, #254, #254, #256, #257, #258. **version 0.3.6 (2009-04-31)** - Change hard coded `/tmp/suds` to `tempfile.gettempdir()` and create `suds/` on demand. - Fix return type for `Any.get_attribute()`. - Update HTTP caching to ignore `file://` URLs. - Better logging of messages when only the reply is injected. - Fix `XInteger` and `XFloat` types to translate returned arrays properly. - Fix `xs:import` schema with same namespace. - Update parser to not load external references and add `Import.bind()` for `XMLSchema.xsd` location. - Add schema doctor - used to patch XSDs at runtime (see `Option.doctor`). - Fix deprecation warnings in python 2.6. - Add behavior for `@default` defined on ``. - Change `@xsi:type` value to always be qualified for doc/literal (reverts 0.3.5 change). - Add `Option.xstq` option to control when `@xsi:type` is qualified. - Fixed Tickets: #64, #129, #205, #206, #217, #221, #222, #224, #225, #228, #229, #230. **version 0.3.5 (2009-04-16)** - Adds HTTP caching. Default is (1) day. Does not apply to method invocation. See: documentation for details. - Removed checking fedora version check in spec since no longer building \< fc9. - Updated makefile to roll tarball with tar.sh. - Moved bare/wrapped determination to WSDL for document/literal. - Refactored `Transport` into a package (provides better logging of HTTP headers). - Fixed Tickets: #207, #209, #210, #212, #214, #215. **version 0.3.4 (2009-02-24)** - Static (automatic) `Import.bind('http://schemas.xmlsoap.org/soap/encoding/')`, users no longer need to do this. - Basic ws-security with {{{UsernameToken}}} and clear-text password only. - Add support for `sparse` SOAP headers via passing dictionary. - Add support for arbitrary user defined SOAP headers. - Fixes service operations with multiple SOAP header entries. - Schema loading and dereferencing algorithm enhancements. - Nested SOAP multirefs fixed. - Better (true) support for `elementFormDefault="unqualified"` provides more accurate namespacing. - WSDL part types no longer default to WSDL `targetNamespace`. - Fixed Tickets: #4, #6, #21, #32, #62, #66, #71, #72, #114, #155, #201. **version 0.3.3 (2008-11-31)** - No longer installs (tests) package. - Implements API-3 proposal (). - Pluggable transport. - Keyword method arguments. - Basic HTTP authentication in default transport. - Add namespace prefix normalization in SOAP message. - Better SOAP message pruning of empty nodes. - Fixed Tickets: #51 - #60. **version 0.3.2 (2008-11-07)** - SOAP {{{MultiRef}}} support `(1st pass added r300)`. - Add support for new schema tags: - `` - `` - `` - `` - Added support for new xs \<\--\> python type conversions: - `xs:int` - `xs:long` - `xs:float` - `xs:double` - Revise marshaller and binding to further sharpen the namespacing of nodes produced. - Infinite recursion fixed in `xsd` package `dereference()` during schema loading. - Add support for `` of schema files into the WSDL root ``. - Fix double encoding of (&). - Add Client API: - `setheaders()` - same as keyword but works for all invocations. - `addprefix()` - mapping of namespace prefixes. - `setlocation()` - override the location in the WSDL; same as keyword except for all calls. - `setproxy()` - same as proxy keyword but for all invocations. - Add proper namespace prefix for SOAP headers. - Fixed Tickets: #5, #12, #34, #37, #40, #44, #45, #46, #48, #49, #50, #51. **version 0.3.1 (2008-10-01)** - Quick follow up to the 0.3 release that made working multi-port service definitions harder then necessary. After consideration (and a good night sleep), it seemed obvious that a few changes would make this much easier: 1) filter out the non-SOAP bindings - they were causing the real trouble; 2) since most servers are happy with any of the SOAP bindings (SOAP 1.1 and 1.2), ambiguous references to methods when invoking then without the port qualification will work just fine in almost every case. So, why not just allow `suds` to select the port. Let us not make the user do it when it is not necessary. In most cases, users on 0.2.9 and earlier will not have to update their code when moving to 0.3.1 as they might have in 0.3. **version 0.3 (2008-09-30)** - Extends the support for multi-port services introduced in 0.2.9. This addition, provides for multiple services to define the *same* method and `suds` will handle it properly. See section \'SERVICES WITH MULTIPLE PORTS:\'. - Add support for multi-document document/literal SOAP binding style. See section \'MULTI-DOCUMENT Document/Literal:\'. - Add support for `xs:group`, `xs:attributeGroup` tags. - Add `Client.last_sent()` and `Client.last_received()`. **version 0.2.9 (2008-09-09)** - Support for multiple ports within a service. - Attribute references ``. - Make XML special character encoder in sax package - pluggable. **version 0.2.8 (2008-08-28)** - Update document/literal binding to always send the document root referenced by the ``. After yet another review of the space and user input, seems like the referenced element is ALWAYS the document root. - Add support for \'binding\' `schemaLocation`s to namespace-uri. This is for imports that do not specify a `schemaLocation` and still expect the schema to be downloaded. E.g. Axis references \'\' without a schemaLocation. So, by doing this: > > from suds.xsd.sxbasic import Import > Import.bind('http://schemas.xmlsoap.org/soap/encoding/') > The schema is bound to a `schemaLocation` and it is downloaded. - Basic unmarshaller does not need a [schema]{.title-ref}. Should have been removed during refactoring but was missed. - Update client to pass kwargs to `send()` and add `location` kwarg for overriding the service location in the WSDL. - Update marshaller to NOT emit XML for object attributes that represent elements and/or attributes that are *both* optional and `value=None`. - Update factory (builder) to include all attributes. - Add `optional()` method to `SchemaObject`. - Update WSDL to override namespace in operation if specified. - Fix schema loading issue - build all schemas before processing imports. - Update packaging in preparation of submission to fedora. **version 0.2.7 (2008-08-11)** - Add detection/support for document/literal - wrapped and unwrapped. - Update document/literal {wrapped} to set document root (under \) to be the wrapper element (w/ proper namespace). - Add support for ``, `` and `` having `maxOccurs` and have the. This causes the unmarshaller to set values for elements contained in an unbounded collection as a list. - Update client.factory (builder) to omit children of `` since the \'user\' really needs to decide which children to include. - Update flattening algorithm to prevent re-flattening of types from imported schemas. - Adjustments to flattening/merging algorithms. **version 0.2.6 (2008-08-05)** - Fix ENUMs broken during `xsd` package overhaul. - Fix type as defined in ticket #24. - Fix duplicate param names in method signatures as reported in ticket #30. - `suds` licensed as LGPL. - Remove logging setup in `suds.__init__()` as suggested by patch in ticket #31. Users will now need to configure the logger. - Add support for `Client.Factory.create()` alt: syntax for fully qualifying the type to be built as: `{namespace}name`. E.g.: > client.factory.create('{http://blabla.com/ns}Person') **version 0.2.5 (2008-08-01)** - Overhauled the `xsd` package. This new (merging) approach is simpler and should be more reliable and maintainable. Also, should provide better performance since the merged schema performs lookups via dictionary lookup. This overhaul should fix current `TypeNotFound` and `` problems, I hope :-). - Fixed dateTime printing bug. - Added infinite recursion prevention in `builder.Builder` for XSD types that contain themselves. **version 0.2.4 (2008-07-28)** - Added support for WSDL imports: ``. - Added support for XSD\<-\>python type conversions (thanks: Nathan Van Gheem) for: - `xs:date` - `xs:time` - `xs:dateTime` - Fixed: - Bug: Schema `` with `schemaLocation` specified. - Bug: Namespaces specified in service description not valid until client/ proxy is printed. **version 0.2.3 (2008-07-23)** - Optimizations. **version 0.2.2 (2008-07-08)** - Update exceptions to be more /standard/ python by using `Exception.__init__()` to set `Exception.message` as suggested by ticket #14; update bindings to raise `WebFault` passing (p). - Add capability in bindings to handle multiple root nodes in the returned values; returned as a composite object unlike when lists are returned. - Fix `soapAction` to be enclosed by quotes. - Add support for ``. - Fix `unbounded()` method in `SchemaObject`. - Refactored schema into new `xsd` package. Files just getting too big. Added `execute()` to `Query` and retrofitted `suds` to `execute()` query instead of using `Schema.find()` directly. Also, moved hokey `start()` methods from schema, as well as, query incrementation. - Add `inject` keyword used to `inject` outbound SOAP messages and/or inbound reply messages. - Refactored SoapClient and 1) rename `send()` to `invoke(`) 2) split message sending from `invoke()` and place in `send()` - Add `TestClient` which allows for invocation kwargs to have `inject={'msg=, and reply='}` for message and reply injection. - Add `Namespace` class to `sax` for better management of namespace behavior; retrofix `suds` to import and use `Namespace`. - Change the default namespace used to resolve referenced types (having attributes `@base=""`, `@type=""`) so that when no prefix is specified: uses XML (node) namespace instead of the `targetNamespace`. - Apply fix as defined by in ticket #13. - Update service definition to print to display service methods as `my_method(xs:int arg0, Person arg1)` instead of `my_method(arg0{xs:int}, arg1{Person})` which is more like traditional method signatures. - Add XSD/python type conversion to unmarshaller (`XBoolean` only); refactor unmarshaller to use `Content` class which makes APIs cleaner, adds symmetry between marshaller(s) and unmarshaller(s), provides good mechanism for schema-property based type conversions. - Refactored marshaller with Appenders; add `nobuiltin` flag to `resolve()` to support fix for `returned_type()` and `returned_collection()` in bindings. - Add support for (202, 204) HTTP codes. - Add `XBoolean` and mappings; add `findattr()` to `TreeResolver` in preparation for type conversions. - Updated schema and schema property loading (deep recursion stopped); Changed `Imported` schemas so then no longer copy imported schemas, rather the import proxies find requests; Add `ServiceDefinition` class which provides better service inspection; also provides namespace mapping and show types; schema property API simplified; support for `xs:any` and `xs:anyType` added; Some schema lookup problems fixed; Binding classes refactored slightly; A lot of debug logging added (might have to comment some out for performance -some of the args are expensive). - Add `sudsobject.Property`; a property is a special `Object` that contains a `value` attribute and is returned by the `Builder` (factory) for schema-types without children such as: `` and ``; `Builder`, `Marshaller` and `Resolver` updated to handle `Properties`; `Resolver` and `Schema` also updated to handle attribute lookups (this was missing). - Add groundwork for user defined SOAP headers. - Fix `elementFormDefault` per ticket #7 - Remove unused kwargs from bindings; cache bindings in WSDL; retrofit legacy `ServiceProxy` to delegate to {new} `Client` API; remove keyword `nil_supported` in favor of natural handling by `nillable` attribute on `` within schemas. - Add support for `` attribute flags (`nillable` and `form`). - Add the `Proxy` (2nd generation API) class. - Add accessor/conversion functions so that users do not need to access `__x__` attributes. Also add `todict()` and `get_items()` for easy conversion to dictionary and iteration. - Search top-level elements for `@ref` before looking deeper. - Add `derived()` to `SchemaObject`. This is needed to ensure that all derived types (WSDL classes) are qualified by `xsi:type` without specifying the `xsi:type` for all custom types as did in earlier `suds` releases. Update the literal marshaller to only add the `xsi:type` when the type needs to be specified. - Change ns promotion in `sax` to prevent ns promoted to parent when parent has the prefix. - Changed binding `returned_type()` to return the (unresolved) `Element`. - In order to support the new features and fix reported bugs, I\'m in the process of refactoring and hopefully evolving the components in `suds` that provide the input/output translations: - `Builder` (translates: XSD objects =\> python objects) - `Marshaller` (translates: python objects =\> XML/SOAP) - `Unmarshaller` (translates: XML/SOAP =\> python objects) This evolution will provide better symmetry between these components as follows: The `Builder` and `Unmarshaller` will produce python (subclass of `sudsobject.Object`) objects with: - `__metadata__.__type__` = XSD type (`SchemaObject`) - subclass name (`__class__.__name__`) = schema-type name and The `Marshaller`, while consuming python objects produced by the `Builder` or `Unmarshaller`, will leverage this standard information to produce the appropriate output (XML/SOAP). The 0.2.1 code behaves *mostly* like this but \... not quite. Also, the implementations have some redundancy. While doing this, it made sense to factor out the common schema-type \"lookup\" functionality used by the `Builder`, `Marshaller` and `Unmarshaller` classes into a hierarchy of `Resolver` classes. This reduces the complexity and redundancy of the `Builder`, `Marshaller` and `Unmarshaller` classes and allows for better modularity. Once this refactoring was complete, the difference between the literal/encoded `Marshallers` became very small. Given that the amount of code in the `bindings.literal` and `bindings.encoded` packages was small (and getting smaller) and in the interest of keeping the `suds` code base compact, I moved all of the marshalling classes to the `bindings.marshaller` module. All of the `bindings.XX` sub-packages will be removed. The net effect: All of the `suds` major components: - client (old: service proxy) - WSDL - schema (xsd package) - resolvers - output (marshalling) - builder - input (unmarshalling) Now have better: - modularity - symmetry with regard to `Object` metadata. - code re-use (\< 1% code duplication \-\-- I hope) - looser coupling and better provide for the following features/bug-fix: - Proper level of XML element qualification based on `` attribute. This will ensure that when `elementFormDefault="qualified"`, `suds` will include the proper namespace on root elements for both literal and encoded bindings. In order for this to work properly, the literal marshaller (like the encoded marshaller) needed to be schema-type aware. Had I added the same schema-type lookup as the encoded marshaller instead of the refactoring described above, the two classes would have been almost a complete duplicate of each other :-( - The builder and unmarshaller used the `schema.Schema.find()` to resolve schema-types. They constructed a path as `person.name.first` to resolve types in proper context. Since `Schema.find()` was stateless, it resolved the intermediate path elements on every call. The new resolver classes are stateful and resolve child types *much* more efficiently. - Prevent name collisions in `sudsobject.Object` like the `items()` method. I\'ve moved all methods (including class methods) to a `Factory` class that is included in the `Object` class as a class attr (`__factory__`). Now that *all* attributes have python built-in naming, we should not have any more name collisions. This of course assumes that no WSDL/schema entity names will have a name with the python built-in naming convention but I have to draw the line somewhere. :-) **version 0.2.1 (2008-05-08)** - Update the `schema.py` `SchemaProperty` loading sequence so that the schema is loaded in 3 steps: 1) Build the raw tree. 2) Resolve dependencies such as `@ref` and `@base`. 3) Promote grandchildren as needed to flatten (denormalize) the tree. The WSDL was also changed to only load the schema once and store it. The schema collection was changed to load schemas in 2 steps: 1) Create all raw schema objects. 2) Load schemas. This ensures that local imported schemas can be found when referenced out of order. The `sax.py` `Element` interface changed: `attribute()` replaced by `get()` and `set()`. Also, `__getitem__()` and `__setitem__()` can be used to access attribute values. Epydocs updated for `sax.py`. And \... last `` now supported properly. - Fix logging by: NOT setting to info in `suds.__init__.logger()`; set handler on root logger only; moved logger (log) from classes to modules and use \_\_name\_\_ for logger name. NOTE: This means that to enable SOAP message logging one should use: > > logger('suds.serviceproxy').setLevel(logging.DEBUG) > instead of: > > logger('serviceproxy').setLevel(logging.DEBUG) > - Add support for XSD schema `` nodes which primarily affects objects returned by the `Builder`. - Update `serviceproxy.py:set_proxies()` to log `DEBUG` instead of `INFO`. - Enhance schema `__str__()` to show both the raw XML and the model (mostly for debugging). **version 0.2 (2008-04-28)** - Contains the first cut at the rpc/encoded SOAP style. - Replaced `Property` class with `suds.sudsobject.Object`. The `Property` class was developed a long time ago with a slightly different purpose. The `suds` `Object` is a simpler (more straight forward) approach that requires less code and works better in the debugger. - The `Binding` (and the encoding) is selected on a per-method basis which is more consistent with the WSDL. In \<= 0.1.7, the binding was selected when the `ServiceProxy` was constructed and used for all service methods. The binding was stored as `self.binding`. Since the WSDL provides for a separate binding style and encoding for each operation, `suds` needed to be change to work the same way. - The `nil_supported` and `faults` flag(s) passed into the service proxy using \*\*kwargs. In addition to these flags, a `http_proxy` flag has been added and is passed to the `urllib2.Request` object. The following args are supported: - `faults` = Raise faults raised by server (default:`True`), else return tuple from service method invocation as (HTTP code, object). - `nil_supported` = The bindings will set the `xsi:nil="true"` on nodes that have a `value=None` when this flag is `True` (default:`True`). Otherwise, an empty node `` is sent. - `proxy` = An HTTP proxy to be specified on requests (default:`{}`). The proxy is defined as `{protocol:proxy,}`. - HTTP proxy supported (see above). - `ServiceProxy` refactored to delegate to a `SoapClient`. Since the service proxy exposes web services via `getattr()`, any attribute (including methods) provided by the `ServiceProxy` class hides WS operations defined by the WSDL. So, by moving everything to the `SoapClient`, WSDL operations are no longer hidden without having to use *hokey* names for attributes and methods in the service proxy. Instead, the service proxy has `__client__` and `__factory__` attributes (which really should be at low risk for name collision). For now the `get_instance()` and `get_enum()` methods have not been moved to preserve backward compatibility. Although, the preferred API change would to replace: > service = ServiceProxy('myurl') > person = service.get_instance('person') with something like: > service = ServiceProxy('myurl') > person = service.__factory__.get_instance('person') After a few releases giving time for users to switch the new API, the `get_instance()` and `get_enum()` methods may be removed with a notice in big letters. - Fixed problem where a WSDL does not define a `` section and `suds` can not resolve the prefixes for the `http://www.w3.org/2001/XMLSchema` namespace to detect builtin types such as `xs:string`. **version 0.1.7 (2008-04-08)** - Added `Binding.nil_supported` to control how property values (out) = `None` and empty tag (in) are processed. - `service.binding.nil_supported = True` \-- means that property values = `None` are marshalled (out) as `` and \ is unmarshalled as `''` and `` is unmarshalled as `None`. - `service.binding.nil_supported = False` \-- means that property values = `None` are marshalled (out) as `` *and* `` is unmarshalled as `None`. The `xsi:nil` is really ignored. - THE DEFAULT IS `True`. - Sax handler updated to handle `multiple character()` callbacks when the sax parser \"chunks\" the text. When the `node.text` is `None`, the `node.text` is set to the characters. Else, the characters are appended. Thanks - \'\'. - Replaced special `text` attribute with `__text__` to allow for natural elements named \"text\". - Add unicode support by: - Add `__unicode__()` to all classes with `__str__()`. - Replace all `str()` calls with `unicode()`. - `__str__()` returns UTF-8 encoded result of `__unicode__()`. - XML output encoded as UTF-8 which matches the HTTP header and supports unicode. - `SchemaCollection` changed to provide the `builtin()` and `custom()` methods. To support this, `findPrefixes()` was added to the `Element` in `sax.py`. This is a better approach anyway since the WSDL and schemas may have many prefixes to \'\'. Tested using both doc/lit and rpc/lit bindings. - Refactored bindings packages from document & rpc to literal & encoded. - Contains the completion of *full* namespace support as follows: - Namespace prefixes are no longer stripped from attribute values that reference types defined in the WSDL. - Schema\'s imported using `` should properly handle namespace and prefix mapping and re-mapping as needed. - All types are resolved, using fully qualified (w/ namespaces) lookups. - `Schema.get_type()` supports paths with and without ns prefixes. When no prefix is specified the type is matched using the schema\'s target namespace. - Property maintains attribute names (keys) in the order added. This also means that `get_item()` and `get_names()` return ordered values. Although, I suspect ordering really needs to be done in the marshaller using the order specified in the WSDL/schema. - Major refactoring of the `schema.py`. The primary goals is preparation for type lookups that are fully qualified by namespace. Once completed, the prefixes on attribute values will no longer be stripped (purged). Change summary: 1) `SchemaProperty` overlay classes created at `__init__()` instead of on-demand. 2) schema imports performed by new `Import` class instead of by `Schema`. 3) Schema loads top level properties using a factory. 4) All `SchemaProperty` /children/ lists are sorted by `__cmp__()` in `SchemaProperty` derived classes. This ensures that types with the same name are resolved in the following order (`Import`, `Complex`, `Simple`, `Element`). 5) All /children/ `SchemaProperty` lists are constructed at `__init__()` instead of on-demand. 6) The SchemaGroup created and WSDL class updated. This works better then having the WSDL aggregate the `` nodes which severs linkage to the WSDL parent element that have namespace prefix mapping. 7) `` element handles properly in that both namespace remapping and prefix re-mapping of the imported schema\'s `targetNamespace` and associated prefix mapping - is performed. E.g. SCHEMA-A has prefix `tns` mapped as `xmlns:tns=http://nsA` and has `targetNamespace='http://nsA'`. SCHEMA-B is importing schema A and has prefix `abc` mapped as `xmlns:abc='http://nsABC'`. SCHEMA-B imports A as ``. So, since SCHEMA-B will be referencing elements of SCHEMA-A with prefix `abc` such as `abc:something`, SCHEMA-A\'s `targetNamespace` must be updated as `http://nsABC` and all elements with `type=tns:something` must be updated to be `type=abc:something` so they can be resolved. - Fixes unmarshalling problem where nodes are added to property as (text, value). This was introduced when the bindings were refactored. - Fixed various `Property` print problems. Notes: > Thanks to Jesper Noehr of Coniuro for the majority of the rpc/literal > binding and for lots of collaboration on `#suds`. **version 0.1.6 (2008-03-06)** - Provides proper handling of WSDLs that contain schema sections containing XSD schema imports: ``. The referenced schemas are imported when a `schemaLocation` is specified. - Raises exceptions for HTTP status codes not already handled. **version 0.1.5 (2008-02-21)** - Provides better logging in the modules get logger by hierarchal names. - Refactored as needed to truly support other bindings. - Add `sax` module which replaces `ElementTree`. This is faster, simpler and handles namespaces (prefixes) properly. **version 0.1.4 (2007-12-21)** - Provides for service method parameters to be `None`. - Add proper handling of method params that are lists of property objects. **version 0.1.3 (2007-12-19)** - Fixes problem where nodes marked as a collection (`maxOccurs` \> 1) not creating property objects with `value=[]` when mapped-in with \< 2 values by the `DocumentReader`. Caused by missing the `` bindings.Document.ReplyHint.stripns()` (which uses ``DocumentReader.stripns()`) conversion to`DocumentReader.stripn()`now returning a tuple`(ns, tag)\`\` as of 0.1.2. **version 0.1.2 (2007-12-18)** - This release contains an update to property adds: - `Metadata` support. - Overrides: `__getitem__`, `__setitem__`, `__contains__`. - Changes property(reader\|writer) to use the `property.metadata` to handle namespaces for XML documents. - Fixes `setup.py` requires. **version 0.1.1 (2007-12-17)** - This release marks the first release in fedora hosted. suds-1.1.2/HACKING.rst000066400000000000000000000717371425611400200143010ustar00rootroot00000000000000GENERAL DEVELOPMENT NOTES ================================================= Project's sources are accessible from a `Git version control repository `_ hosted at Github. Project development should be tracked in the ``TODO.txt`` file. * Exact formatting is not important as long as its content is kept formatted consistently. * Done tasks should be marked as such and not deleted. Separate sections below: * `TOP-LEVEL PROJECT FILES & FOLDERS`_ * `PYTHON COMPATIBILITY`_ * `RELEASE PROCEDURE`_ * `DEVELOPMENT & TESTING ENVIRONMENT`_ * `PYTHON 2/3 SOURCE CODE COMPATIBILITY`_ * `EXTERNAL DOCUMENTATION`_ * `STANDARDS CONFORMANCE`_ * `PROJECT IMPLEMENTATION NOTES`_ * `REPRODUCING PROBLEMATIC USE CASES`_ For additional design, research & development project notes see the project's ``notes/`` folder. TOP-LEVEL PROJECT FILES & FOLDERS ================================================= | build/ | dist/ | suds_jurko.egg-info/ * Folders created during project setup procedure (build/install). | notes/ * Internal project design, research & development notes. | suds/ * Basic project source code. | tests/ * Project test code. | tools/ * Project development & setup utility scripts. Related internal Python modules are located under its ``suds_devel/`` package folder. | MANIFEST.in * Build system configuration file listing the files to be included in the project's source distribution packages in addition to those automatically added to those packages by the used package preparation system. | HACKING.rst | LICENSE.txt | README.txt | TODO.txt * Internal project documentation. | setup.cfg * Basic project Python configuration. | setup.py * Standard Python project setup script. * Usage examples: ``setup.py --help`` show detailed usage information ``setup.py --help-commands`` show detailed ``setup.py`` command list ``setup.py build`` build the project ``setup.py install`` build & install the project ``setup.py register`` register a project release at PyPI ``setup.py sdist`` prepare a source distribution ``setup.py upload`` upload prepared packages to PyPI * Usage examples requiring ``setuptools``: ``setup.py develop`` prepare the development environment (add the project folder to the Python module search path) the same as if installed using ``easy_install -e`` or ``pip install -e`` ``setup.py test`` run the project test suite (requires ``pytest``) PYTHON COMPATIBILITY ================================================= Base sources should remain Python 2.x compatible. Since the original project states aiming for Python 2.4 compatibility we do so as well. The Python 3.0 minor release is not supported. See `Python 3.0 support`_ subsection below for more detailed information. Test & setup code needs to be implemented using Python 2 & 3 compatible source code. Setup & setup related scripts need to be implemented so they do not rely on other pre-installed libraries. These backward compatibility requirements do not affect internal development operations such as ``setup.py`` support for uploading a new project distribution package to PyPI. Such operations need to be supported on the latest Python 2 & 3 releases only and no additional backward compatibility is either tested or guaranteed for them. The following is a list of backward incompatible Python features not used in this project to maintain backward compatibility: Features missing prior to Python 2.5 ------------------------------------ * ``any`` & ``all`` functions. * ``with`` statement. * BaseException class introduced and KeyboardInterrupt & SystemExit exception classes stopped being Exception subclasses. * This means that code wanting to support Python versions prior to this release needs to re-raise KeyboardInterrupt & SystemExit exceptions before handling the generic 'Exception' case, unless it really wants to gobble up those special infrastructural exceptions as well. * ``try``/``except``/``finally`` blocks. * Prior to this Python release, code like the following:: try: A except XXX: B finally: C was considered illegal and needed to be written using nested ``try`` blocks as in:: try: try: A except XXX: B finally: C * ``yield`` expression inside a ``try`` block with a ``finally`` clause. * Prior to this Python release, code like the following:: try: yield x finally: do_something() is considered illegal, but can be replaced with legal code similar to the following:: try: yield x except: do_something() raise do_something() Features missing prior to Python 2.6 ------------------------------------ * ``bytes`` type. * Byte literals, e.g. ``b"quack"``. * Class decorators. * ``fractions`` module. * ``numbers`` module. * String ``format()`` method. * Using the ``with`` statement from Python 2.5.x requires the ``from __future__ import with_statement``. Features missing prior to Python 2.7 ------------------------------------ * Dictionary & set comprehensions. * Set literals. Features missing in Python 3.0 & 3.1 ------------------------------------ * py2to3 conversion for source files with an explicitly specified UTF-8 BOM. Python 3.0 support ------------------ Python 3.0 release has been marked as deprecated almost immediately after the release 3.1. It is not expected that this Python release is actively used anywhere in the wild. That said, if anyone really wants this version supported - patches are welcome. At least the following problems have been found with Python 3.0: * None of the tools required to properly test our project (setuptools, pip, virtualenv, tox, etc.) will work on it. * When you attempt to setuptools project with Python 3.0, it attempts to use the ``sys.stdout.detach()`` method introduced only in Python 3.1. This specific issue could be worked around by using ``sys.stdout.buffer`` directly but the actual fix has not been attempted. If anyone wants to take this route though and work on supporting setuptools on Python 3.0 - be warned that it will most likely have other issues after this one as well. * When applying py2to3 to the project sources, Python will use the current user's locale encoding instead of the one specified in the project sources, thus causing the operation to fail on some source files containing different unicode characters unless the user's environement uses some sort of unicode encoding by default, e.g. will fail on some test scripts when run on Windows with eastern European regional settings (uses the CP1250 encoding). RELEASE PROCEDURE ================================================= 1. Document the release correctly in ``README.rst``. 2. Test the project build with the latest available ``setuptools`` project and update the ``ez_setup.py`` ``setuptools`` installation script as needed. * Use the latest available & tested ``setuptools`` release. * If a new ``setuptools`` release drops support for an older Python release, update our ``setup.py`` script to use an older ``setuptools`` installation script when run using the no longer supported Python release. * For example, ``setuptools`` version 2.0 dropped support for Python 2.4 & 2.5 and so ``setup.py`` uses a separate ``ez_setup_1_4_2.py`` ``setuptools`` installation script with Python versions older than 2.6. 3. Version identification. * Official releases marked with no extra suffix after the basic version number. * Alfa releases marked with the suffix ``.a#``. * Beta releases marked with the suffix ``.b#``. * Release candidate releases marked with the suffix ``.rc#``. * Development releases marked with the suffix ``.dev#``. * Version ordering (as recognized by pip & setuptools):: 0.5.dev0 < 0.5.dev1 < 0.5.dev5 < 0.5.a0.dev0 < 0.5.a0.dev5 < 0.5.a0 < 0.5.a3.dev0 < 0.5.a3.dev5 < 0.5.a3 < 0.5.b0.dev0 < 0.5.b0.dev5 < 0.5.b0 < 0.5.b3.dev0 < 0.5.b3.dev5 < 0.5.b3 < 0.5.rc0.dev0 < 0.5.rc0.dev5 < 0.5.rc0 < 0.5.rc3.dev0 < 0.5.rc3.dev5 < 0.5.rc3 < 0.5 < 0.5.1.dev0 < ... ... < 0.5.1 < 0.6.dev0 < ... ... < 0.6 < 1.0.dev0 < ... ... < 1.0 4. Tag in Hg. * Name the tag like ``release-``, e.g. ``release-0.5``. 5. Prepare official releases based only on tagged commits. * Official releases should always be prepared based on tagged revisions with no local changes in the used sandbox. * Prepare source distribution packages (both .zip & .tar.bz2 formats) and upload the prepared source packages to PyPI. * Run ``setup.py sdist upload``. * Prepare wheel packages for Python 2 & 3 using the latest Python 2 & 3 environments with the ``wheel`` package installed and upload them to PyPI. * Run ``setup.py bdist_wheel upload`` using both Python 2 & 3. * Upload the prepared source & wheel packages to the project site. * Use the BitBucket project web interface. 6. Next development version identification. * If this was a development release. * Bump up the existing ``.dev#`` suffix, e.g. change ``0.8.dev2`` to ``0.8.dev3``. * If this was a non-development release. * Bump up the forked project version counter (may add/remove/bump alfa/beta/release-candidate mark suffixes as needed). * Add the ``.dev0`` suffix, e.g. as in ``0.8.dev0``. 7. Notify whomever the new release might concern. DEVELOPMENT & TESTING ENVIRONMENT ================================================= In all command-line examples below pyX, pyXY & pyXYZ represent a Python interpreter executable for a specific Python version X, X.Y & X.Y.Z respectively. Setting up the development & testing environment ------------------------------------------------ ``tools/setup_base_environments.py`` script should be used for setting up the basic Python environments so they support testing our project. The script can be configured from the main project Python configuration file ``setup.cfg``. It implements all the backward compatibility tweaks and performs additional required package installation that would otherwise need to be done manually in order to be able to test our project in those environments. These exact requirements and their related version specific tweaks are not documented elsewhere so anyone interested in the details should consult the script's sources. The testing environment is generally set up as follows: 1. Install clean target Python environments. #. Update the project's ``setup.py`` configuration with information on your installed Python environments. #. Run the ``tools/setup_base_environments.py`` script. Some older Python environments may have slight issues caused by varying support levels in different used Python packages, but the basic testing functionality has been tested to make sure it works on as wide array of supported platforms as possible. Examples of such issues: * Colors not getting displayed on a Windows console terminal, with possibly ANSI color code escape sequences getting displayed instead. * ``pip`` utility can not be run from the command-line using the ``py -m pip`` syntax for some older versions. In such cases use the more portable ``py -c "import pip;pip.main()"`` syntax instead. * Some specific older Python versions (e.g. 2.4.3) have no SSL support and so have to reuse installations downloaded by other Python versions. Running the project tests - ``tools/run_all_tests.py`` script ------------------------------------------------------------- ``tools/run_all_tests.py`` script is a basic *poor man's tox* development script that can be used for running the full project test suite using multiple Python interpreter versions on a development machine. Intended to be replaced by a more portable ``tox`` based or similar automated testing solution some time in the future. Can be configured by tweaking the main project Python configuration file ``setup.cfg``: * List of target Python environments. * Each target Python environment's invocation command. Requires the target Python environment already be set up, and all the packages required for running the project test suite installed. See the `Setting up the development & testing environment`_ section for more detailed information. Automatically installs the project in editable mode in all tested Python environments. Caveats: * This method does not allow you to provide any extra ``pytest`` options when running the project test suite. Running the project tests - ``setup.py test`` command ----------------------------------------------------- Project tests can also be run for a specific Python environment by running the project's ``setup.py`` script in that environment and invoking its ``test`` command. E.g. run a command like one of the following ones from the top level project folder:: py243 setup.py test py27 setup.py test py3 setup.py test Note that the ``setup.py`` script always needs to be called from the top level project folder. For most Python versions, the target Python environment needs not be set up prior to running this command. Where possible (e.g. not for Python 2.4.x or 3.1.x versions), any missing testing requirements will be installed automatically, but not directly into the target environment but in the current folder instead. This functionality should be considered a band-aid though, and setting up the target environment can be better done as described in the `Setting up the development & testing environment`_ section. The ``setup.py test`` command will build the project if needed and run its test suite in the target Python environment. The project does not need to be preinstalled into the target Python environment for this operation to work, and neither will the operation leave it installed. Unless a more restricted test set is selected using ``pytest`` specific command-line options, ``setup.py test`` command runs the complete project test suite. Specific ``pytest`` command-line options may be provided by passing them all as a single whitespace separated string tunnelled via the ``setup.py test`` command's ``--pytest-args``/``-a`` command-line option. For example, the following command will run only tests containing ``binding`` in their name, will stop on first failure and will automatically drop into Python's post-mortem debugger on failure:: setup.py test -a "-k binding -x --pdb" Caveats: * This method does not currently allow passing ``pytest`` specific command-line options containing embedded whitespace. * When running the ``setup.py test`` command in a Windows Python 2.5 environment without an included ctypes module (e.g. 64-bit CPython 2.5 distribution does not include ctypes) and having it automatically install the colorama package version older than 0.1.11, you will get benign error messages reporting colorama's atexit handlers failing. Running the same command again avoids the issue since the colorama package will then already be installed. Suggested workaround is to use a colorama package version 0.3.2 or newer. Running the project tests - using ``pytest`` directly ----------------------------------------------------- To have greater control over the test suite and be able to specify additional ``pytest`` options on the command-line, or be able to run the tests on a different project installation (e.g. official release installed directly from PyPI), do the following: 1. Install the project into the target Python environment. * Installing the project can be done by either installing it directly into the target Python environment using one of the following commands (paths used assume the commands are being run from the top level project folder):: setup.py install easy_install . pip install . Or the project can be installed in editable mode using one of the following commands (so it does not need to be reinstalled after every source code change):: setup.py develop easy_install -e . pip install -e . * The installation step can be skipped if running Python 2 based project tests, and doing so from the top level project folder. 2. Run tests using ``pytest``. * If using Python 2.x: * Run ``pytest`` from the project's top level or ``tests`` folder:: py2 -m pytest * If using Python 3.x: * Since the project uses py2to3 source conversion, you need to build the project in order to generate the project's Python 3 sources before they can be tested. If the project has been installed in editable mode, then simply run the following from the top level project folder:: setup.py build and if it has not then rebuild and reinstall it using one of the following commands:: setup.py develop setup.py install Note that you might need to manually remove the build folder in order to have its contents regenerated when wanting to run the test suite using a different Python 3.x interpreter version, as those sources are regenerated based solely on the original & processed source file timestamp information and not the Python version used to process them. * Run ``pytest`` from the the project's ``tests`` folder:: py3 -m pytest Each specific test module can also be run directly as a script. Notes on the folder from which to run the tests: * When running tests from a folder other than the top level project folder, the tested project version needs to first be installed in the used Python environment. * Python 2 tests can be run from the top level project folder, in which case they will work even if the project has not been explicitly installed in the used Python environment. And even if another project version has been installed into the used Python environment, that one will be ignored and the one in the current folder used instead. * Python 3 tests can not be run from the top level project folder or they would attempt and fail to use Python 2 based project sources found in the current folder. See the ``pytest`` documentation for a detailed list of available command-line options. Some interesting ones: -l show local variable state in tracebacks --tb=short shorter traceback information for each failure -x stop on first failure --pdb enter Python debugger on failure Setting up multiple parallel Python interpreter versions on Windows ------------------------------------------------------------------- On Windows you might have a problem setting up multiple parallel Python interpreter versions in case their major and minor version numbers match, e.g. Python 2.4.3 & 2.4.4. In those cases, standard Windows installer will automatically remove the previous installation instead of simply adding a new one. In order to achieve such parallel setup we suggest the following steps: 1. Install the first version in a dummy folder, and do so for the current user only. #. Copy the dummy target folder to the desired folder for the first installation, e.g. Python243. #. Uninstall the original version. #. Set up a shortcut or a batch script (e.g. py243.cmd) for running this interpreter without having to have it added to the system path. #. Repeat the steps for the second installation. Installing Python for the current user only is necessary in order to make Python install all of its files into the target folder and not move some of them into shared system folders. Note that this will leave you without start menu or registry entries for these Python installations. Registry entries should be needed only if you want to run some external Python package installation tool requiring those entries in order to determine where to install its package data. In that case you can set those entries manually, e.g. by using a script similar to the one found at ``_. PYTHON 2/3 SOURCE CODE COMPATIBILITY ================================================= These are notes related to maintaining Python 2/3 source code compatibility in parts of this project that require it. Use the ``six `` Python 2/3 compatibility support package to make the compatibility patches simpler. Where a solution provided by ``six`` can not be used, explicitly explain the reason why in a related code comment. Do not use ``u"..."`` Python unicode literals since we wish to support Python 3.1 & 3.2 versions which do not support them. Useful site for easily converting unicode strings to their ``unicode-escape`` encoded representation which can then be used with the ``six.u()`` helper function: http://www.rapidmonkey.com/unicodeconverter EXTERNAL DOCUMENTATION ================================================= * SOAP * http://www.w3.org/TR/soap * Version 1.1. * http://www.w3.org/TR/2000/NOTE-SOAP-20000508 * Version 1.2. * Part0: Primer * http://www.w3.org/TR/2007/REC-soap12-part0-20070427 * Errata: http://www.w3.org/2007/04/REC-soap12-part0-20070427-errata.html * Part1: Messaging Framework * http://www.w3.org/TR/2007/REC-soap12-part1-20070427 * Errata: http://www.w3.org/2007/04/REC-soap12-part1-20070427-errata.html * Part2: Adjuncts * http://www.w3.org/TR/2007/REC-soap12-part2-20070427 * Errata: http://www.w3.org/2007/04/REC-soap12-part2-20070427-errata.html * Specification Assertions and Test Collection * http://www.w3.org/TR/2007/REC-soap12-testcollection-20070427 * Errata: http://www.w3.org/2007/04/REC-soap12-testcollection-20070427-errata.html * WS-I Basic Profile 1.1 * http://www.ws-i.org/Profiles/BasicProfile-1.1.html * WSDL 1.1 * http://www.w3.org/TR/wsdl * XML Schema * Part 0: Primer Second Edition - http://www.w3.org/TR/xmlschema-0 * Non-normative document intended to provide an easily readable description of the XML Schema facilities, and is oriented towards quickly understanding how to create schemas using the XML Schema language. * Part 1: Structures - http://www.w3.org/TR/xmlschema-1 * Part 2: Datatypes - http://www.w3.org/TR/xmlschema-2 STANDARDS CONFORMANCE ================================================= There seems to be no complete standards conformance overview for the suds project. This section contains just some related notes, taken down while hacking on this project. As more related information is uncovered, it should be added here as well, and eventually this whole section should be moved to the project's user documentation. Interpreting message parts defined by a WSDL schema --------------------------------------------------- * Each message part is interpreted as a single parameter. * What we refer to here as a 'parameter' may not necessarily correspond 1-1 to a Python function argument passed when using the suds library's Python function interface for invoking web service operations. In some cases suds may attempt to make the Python function interfaces more intuitive to the user by automatically unwrapping a parameter as defined inside a WSDL schema into multiple Python function arguments. * In order to achieve interoperability with existing software 'in the wild', suds does not fully conform to the WSDL 1.1 specification with regard as to how message parts are mapped to input data contained in SOAP XML web service operation invocation request documents. * WSDL 1.1 standard states: * 2.3.1 Message Parts. * A message may have message parts referencing either an element or a type defined in the WSDL's XSD schema. * If a message has a message part referencing a type defined in the WSDL's XSD schema, then that must be its only message part. * 3.5 soap:body. * If using document/literal binding and a message has a message part referencing a type defined in the WSDL's XSD schema then that part becomes the schema type of the enclosing SOAP envelope Body element. * Suds supports multiple message parts, each of which may be related either to an element or a type. * Suds uses message parts related to types, as if they were related to an element, using the message part name as the representing XML element name in the constructed related SOAP XML web service operation invocation request document. * WS-I Basic Profile 1.1 standard explicitly avoids the issue by stating the following: * R2204 - A document/literal binding in a DESCRIPTION MUST refer, in each of its soapbind:body element(s), only to wsdl:part element(s) that have been defined using the element attribute. * Rationale. * No other software has been encountered implementing the exact functionality specified in the WSDL 1.1 standard. * Already done in the original suds implementation. * Example software whose implementation matches our own. * SoapUI. * Tested with version 4.6.1. * WSDL analyzer & invoker at ``_. WSDL XSD schema interpretation ------------------------------ * ``minOccurs``/``maxOccurs`` attributes on ``all``, ``choice`` & ``sequence`` schema elements are ignored. * Rationale. * Already done in the original suds implementation. * Extra notes. * SoapUI (tested with version 4.6.1). * For ``all``, ``choice`` & ``sequence`` schema elements with their ``minOccurs`` attribute set to "0", does not explicitly mark elements found in such containers as optional. * Supports sending multiple same-named web service operation parameters, but only if they are specified next to each other in the constructed web service operation invocation request document. * Done by passing a list or tuple of such values to the suds constructed Python function representing the web service operation in question. * Rationale. * Already done in the original suds implementation. * Extra notes. * Such same-named values break other web service related tools as well, e.g. WSDL analyzer & invoker at ``_. PROJECT IMPLEMENTATION NOTES ================================================= Sometimes we have a reason for implementing a feature in a certain way that may not be obvious at first and which thus deserves an implementation comment explaining the rationale behind it. In cases when such rationale would then be duplicated at different places in code, and project implementation note should be added and identified here, and its respective implementation locations marked using a comment such as:: # See 'Project implementation note #42'. Project implementation note #1 ------------------------------- ``pytest`` test parametrizations must be defined so they get ordered the same in different test processes. Doing otherwise may confuse the ``pytest`` ``xdist`` plugin used for running parallel tests using multiple test processes (last tested using ``pytest 2.5.2``, ``xdist 1.10`` & ``execnet 1.2.0``) and may cause it to exit with errors such as:: AssertionError: Different tests were collected between gw1 and gw0 Specifically, this means that ``pytest`` test parametrizations should not be constructed using iteration over unordered collections such as sets or dictionaries, at least not with Python's hash randomization feature enabled (implemented as optional since Python 2.6.8, enabled by default since Python 3.3). See the following ``pytest`` issues for more detailed information: * `#301 `_ - serializing collection process (per host) on xdist to avoid conflicts/collection errors * `#437 `_ - different tests collected on two nodes with xdist REPRODUCING PROBLEMATIC USE CASES ================================================= Failing web service processing examples can be easily packaged as reproducible test cases using the suds library 'message & reply injection' technique. Some things you can achieve using this technique (for examples, see existing project unit tests): * Create a client object based on a fixed WSDL string. * Have a client object send a fixed request string without having it construct one based on the loaded WSDL schema and received arguments. * Have a client object process a fixed reply string without having it send a request to an actual external web service. suds-1.1.2/LICENSE.txt000066400000000000000000000167331425611400200143210ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. suds-1.1.2/MANIFEST.in000066400000000000000000000030771425611400200142310ustar00rootroot00000000000000# Additional files to be included in the source distribution package (created # by running 'setup.py sdist'). Theoretically we could avoid having to manually # maintain this list by using a setuptools plugin that would automatically # include all files under Mercurial version control, but the setuptools_hg we # tried out did not work correctly with Python 3. # Top level project files. include ez_setup.py include ez_setup_1_4_2.py include HACKING.rst include LICENSE.txt include TODO.txt # Notes. include notes/*.rst include notes/*.txt # Tests. recursive-include tests *.py # Project development & setup tools. include tools/*.py include tools/*.txt recursive-include tools/suds_devel *.py # Python 2 versions prior to some early 2.7.x release and Python 3 versions # prior to some 3.2.x release had buggy disutils implementations that can # result in our project's source distribution containing some extra unwanted # files picked up from some of our local cache folders. This is a 3-layer fix # to work around the problem: # 1. We prune those folders just in case some of their content got added by # mistake. # 2. An extra include is here to silence distutils warnings in case the used # distutils implementation is not buggy and therefore no extra files have # been added and distutils can not find anything to prune. # 3. To make the include actually include an existing file, setup.py # constructs at least one such file to be included with a buggy distutils # implementation. include tools/__*/* prune tools/__* suds-1.1.2/README.md000066400000000000000000001252401425611400200137470ustar00rootroot00000000000000![Build Status](https://github.com/suds-community/suds/workflows/Test/badge.svg?branch=master) # Overview Suds is a lightweight SOAP-based web service client for Python licensed under LGPL (see the `LICENSE.txt` file included in the distribution). Although the original `suds` package stopped releasing versions after `0.4`, many (but not all) other open source projects moved to a maintained fork known as "suds-jurko". This is a community fork of that fork that is releasing packages under the main `suds` package name (and `suds-community` for consistency until version 2.x of this package). **Forked project information** - Project site - - Official releases can be downloaded from: - Github - - PyPI - and **Original suds Python library development project information** For development notes see the `HACKING.rst` document included in the distribution. # Installation Standard Python installation. Here are the basic instructions for 3 different installation methods: 1. Using `pip` - Have the `pip` package installed. - Run `pip install suds`. 2. Using `easy-install` - Have the `setuptools` package installed. - Run `easy_install suds`. 3. From sources - Unpack the source package somewhere. - Run `python setup.py install` from the source distribution\'s top level folder. ## Installation troubleshooting - Released prior to `0.7` have many known installation issues requiring the target Python environment to be manually prepared when using some ancient Python versions, e.g. 2.4, 2.5 or 3.1. - Releases `0.4.1. jurko 5 < x <= 0.6` may not be installed using `pip` into a Python environment with an already installed `setuptools` package older than the version expected by our project. Displayed error message includes instructions on how to manually upgrade the installed `setuptools` package before rerunning our installation. - `pip` internally imports existing `setuptools` packages before running our setup, thus preventing us from upgrading the existing `setuptools` installation inplace. - If automated `setuptools` Python package installation fails (used in releases `0.4.1 jurko 5` and later), e.g. due to PyPI web site not being available, user might need to install it manually and then rerun the installation. - Releases prior to `0.4.1. jurko 5` will fail if the `distribute` Python package is not already installed on the system. - Python 2.4.3 on Windows has problems using automated `setuptools` Python package downloads via the HTTPS protocol, and therefore does not work correctly with PyPI which uses HTTPS links to all of its packages. The same does not occur when using Python version 2.4.4. # Features Basic features: - No class generation - Provides an object-like API. - Reads wsdl at runtime for encoding/decoding - Provides for the following SOAP (style) binding/encoding: - Document/Literal - RPC/Literal - RPC/Encoded (section 5) The goal of suds is to present an RPC-like interface into soap-based web services. This means that in most cases, users do not need to be concerned with the complexities of the WSDL and referenced schemas. Regardless of which soap message style is specified, the signature of the service methods remain the same. Uses that do examine the WSDL will notice that even with the document soap message style, the signature of each method resembles an RPC. The method signature contains the contents of the document defined for the message instead of the document itself. The primary interface into the library is the `Client` object. It provides methods for configuring the library and (2) sub-namespaces defined below. When the `Client` is created, it processes the wsdl and referenced schema(s). From this information, it derives a representation of this information which is used to provide the user with a service description and for message/reply processing. ## Python Support See `.github/workflows/test_and_release.yml` for supported Python versions. The goal is to support [currently maintained versions of Python](https://devguide.python.org/#status-of-python-branches). ## Logging The suds package use the Python standard lib logging package: all messages are at level DEBUG or ERROR. To register a console handler you can use basicConfig: ```py import logging logging.basicConfig(level=logging.INFO) ``` Once the console handler is configured, the user can enable module specific debugging doing the following: logging.getLogger(\).setLevel(logging.\) A common example (show sent/received soap messages): ```py logging.getLogger('suds.client').setLevel(logging.DEBUG) ``` Suggested modules for debugging: * suds.client:: Set the logging level to DEBUG on this module to see soap messages (in & out) and http headers. * suds.transport:: Set the logging level to DEBUG on this module to see more details about soap messages (in& out) and http headers. * suds.xsd.schema:: Set the logging level to DEBUG on this module to see digestion of the schema(s). * suds.wsdl:: Set the logging level to `DEBUG` on this module to see digestion WSDL. ## Basic Usage Version: API\^3\^ The `suds` `Client` class provides a consolidated API for consuming web services. The object contains (2) sub-namespaces: __service__:: The `service` namespace provides a proxy for the consumed service. This object is used to invoke operations (methods) provided by the service endpoint. __factory__:: The `factory` namespace provides a factory that may be used to create instances of objects and types defined in the WSDL. You will need to know the url for WSDL for each service used. Simply create a client for that service as follows: ```py from suds.client import Client url = 'http://localhost:7080/webservices/WebServiceTestBean?wsdl' client = Client(url) ``` You can inspect service object with: `__str()__` as follows to get a list of methods provide by the service: ```py print client Suds - version: 0.3.3 build: (beta) R397-20081121 Service (WebServiceTestBeanService) tns="http://test.server.enterprise.rhq.org/" Prefixes (1): ns0 = "http://test.server.enterprise.rhq.org/" Ports (1): (Soap) Methods: addPerson(Person person, ) echo(xs:string arg0, ) getList(xs:string str, xs:int length, ) getPercentBodyFat(xs:string name, xs:int height, xs:int weight) getPersonByName(Name name, ) hello() testExceptions() testListArg(xs:string[] list, ) testVoid() updatePerson(AnotherPerson person, name name, ) Types (23): Person Name Phone AnotherPerson ``` note: See example of service with multiple ports below. The sample output lists that the service named `WebServiceTestBeanService` has methods such as getPercentBodyFat() and addPerson(). #### Simple Arguments Let\'s start with the simple example. The getPercentBodyFat() method has the signature of getPercentBodyFat(`xs:string` name, `xs:int` height, `xs:int` weight). In this case, the parameters are `simple` types. That is, they not objects. This method would be invoked as follows: ```py result = client.service.getPercentBodyFat('jeff', 68, 170) print result You have 21% body fat. ``` ```py result = client.service.getPercentBodyFat(name='jeff', height=68, weight=170) print result You have 21% body fat. ``` ```py d = dict(name='jeff', height=68, weight=170) result = client.service.getPercentBodyFat(**d) print result You have 21% body fat. ``` #### Complex Arguments The addPerson() method takes a `person` argument of type: `Person` and has a signature of: addPerson(`Person` person, ) where parameter type is printed followed by it\'s name. There is a type (or class) named \'person\' which is coincidentally the same name as the argument. Or in the case of getPercentBodyFat() the parameters are __string__ of type xs:string and __integer__ of type xs:int. So, to create a `Person` object to pass as an argument we need to get a person argument using the `factory` sub-namespace as follows: ```py person = client.factory.create('Person') print person (Person)= { phone = [] age = NONE name(Name) = { last = NONE first = NONE } } ``` As you can see, the object is created as defined by the WSDL. The list of phone number is empty so we\'ll have to create a `Phone` object: ```py phone = client.factory.create('Phone') phone.npa = 202 phone.nxx = 555 phone.number = 1212 ``` \... and the name (Name object) and age need to be set and we need to create a name object first: ```py name = client.factory.create('Name') name.first = 'Elmer' name.last = 'Fudd' ``` Now, let\'s set the properties of our `Person` object ```py person.name = name person.age = 35 person.phone = [phone] ``` or ```py person.phone.append(phone) \... and invoke our method named addPerson() as follows: ```py try: person_added = client.service.addPerson(person) except WebFault as e: print e ``` It\'s that easy. The ability to use python `dict` to represent complex objects was re-introduced in 0.3.8. However, this is not the preferred method because it may lead to passing incomplete objects. Also, this approach has a significant limitation. Users may __not__ use python `dict` for complex objects when they are subclasses (or extensions) of types defined in the wsdl/schema. In other words, if the schema defines a type to be an `Animal` and you wish to pass a `Dog` (assumes Dog `isa` Animal), you may __not__ use a `dict` to represent the dog. In this case, suds needs to set the xsi:type=\"Dog\" but cannot because the python `dict` does not provide enough information to indicate that it is a `Dog` not an `Animal`. Most likely, the server will reject the request and indicate that it cannot instantiate a abstract `Animal`. #### Complex Arguments Using Python (dict) Note: version 0.3.8+ Just like the factory example, let\'s assume the addPerson() method takes a `person` argument of type: `Person`. So, to create a `Person` object to pass as an argument we need to get a person object and we can do so by creating a simple python `dict`. ```py person = {} ``` According to the WSDL we know that the Person contains a list of Phone objects so we\'ll need `dict`s for them as well. ```py phone = { 'npa':202, 'nxx':555, 'number':1212, } ``` \... and the name (Name object) and age need to be set and we need to create a name object first: ```py name = { 'first':'Elmer', 'last':'Fudd' } ``` Now, let\'s set the properties of our `Person` object ```py person['name'] = name person['age'] = 35 person['phone'] = [phone,] ``` \... and invoke our method named addPerson() as follows: ```py try: person_added = client.service.addPerson(person) except WebFault as e: print e ``` ### Faults The Client can be configured to throw web faults as `WebFault` or to return a tuple (\, \) instead as follows: ```py client = client(url, faults=False) result = client.service.addPerson(person) print result ( 200, person ...) ``` ### Options The `suds` `client` has many that may be used to control the behavior of the library. Some are `general options` and others are `transport options`. Although, the options objects are exposed, the preferred and supported way to set/unset options is through: - The `Client` constructor - The `Client`.set\_options() - The `Transport` constructor(s). General options are as follows: * faults:: Controls web fault behavior. * service:: Controls the default service name for multi-service wsdls. * port:: Controls the default service port for multi-port services. * location:: This overrides the service port address URL defined in the WSDL. * transport:: Controls the plugin web `transport`. * cache:: Provides caching of documents and objects related to loading the WSDL. Soap envelopes are never cached. * cachingpolicy:: The caching policy, determines how data is cached. The default is 0. version 0.4+ - 0 = XML documents such as WSDL & XSD. - 1 = WSDL object graph. * soapheaders:: Provides for soap headers. * wsse:: Provides for WS-Security object. \ * __inject`:: Controls message/reply message injection. * doctor:: The schema `doctor` specifies an object used to fix broken schema(s). * xstq:: The XML schema type qualified flag indicates that `xsi:type` attribute __values__ should be qualified by namespace. * prefixes:: Elements of the soap message should be qualified (when needed) using XML prefixes as opposed to xmlns=\"\" syntax. * retxml:: Flag that causes the I{raw} soap envelope to be returned instead of the python object graph. * autoblend:: Flag that ensures that the schema(s) defined within the WSDL import each other. * nosend:: Flag that causes suds to generate the soap envelope but not send it. Instead, a `RequestContext` is returned Default: False. * sortnamespaces:: Flag that causes suds to sort namespaces alphabetically on storing them. Default: True. * allowUnknownMessageParts:: Raise exceptions when unknown message parts are detected when receiving a web service reply, compared to the operation's WSDL schema definition. Default: False. **Transport options passed to the `Client` contructor are only used if the default transport is used**, they are as follows: * proxy:: Controls http proxy settings. * timeout:: The URL connection timeout (seconds) default=90. A timeout can also be set per method invocation. * headers:: Provides for `extra` http headers. * username:: username for HTTP authentication. * password:: password for HTTP authentication. ### Custom behavior through swapping components Various ways that suds behaves can be customized by swapping out custom #### Initializing optional arrays with lists In some unreleased versions of suds-jurko, all children elements were populated with empty lists. This was fixed in suds-community as a regression. This can be desired behavior as it simplifies constructing complex requests. To obtain the prior behavior, use a custom `Builder` class, for example: ``` from suds.client import Client, Builder class AlwaysInitializeBuilder(Builder): def skip_value(self, type): return False # always set value client = Client(url) client.factory.builder = AlwaysInitializeBuilder(client.factory.builder.resolver) ``` ## Enumerations Enumerations are handled as follows: Let\'s say the wsdl defines the following enumeration: ```xml ``` The client can instantiate the enumeration so it can be used. Misspelled references to elements of the `enum` will raise a `AttrError` exception as: ```py resourceCategory = client.factory.create('resourceCategory') client.service.getResourceByCategory(resourceCategory.PLATFORM) ``` ## Factory The `factory` is used to create complex objects defined the the wsdl/schema. This is __not__ necessary for parameters or types that are specified as `simple` types such as xs:string, xs:int, etc \... The create() method should always be used becuase it returns objects that already have the proper structure and schema-type information. Since xsd supports nested type definition, so does create() using the (.) dot notation. For example suppose the (Name) type was not defined as a top level \"named\" type but rather defined within the (Person) type. In this case creating a (Name) object would have to be quanified by it\'s parent\'s name using the dot notation as follows: ```py name = client.factory.create('Person.Name') ``` If the type is in the same namespace as the wsdl (targetNamespace) then it may be referenced without any namespace qualification. If not, the type must be qualifed by either a namespace prefix such as: ```py name = client.factory.create('ns0:Person') ``` Or, the name can be fully qualified by the namespace itself using the full qualification syntax as (as of 0.2.6): ```py name = client.factory.create('{http://test.server.enterprise.rhq.org/}person') ``` Qualified names can only be used for the first part of the name, when using (.) dot notation to specify a path. ## Services With Multiple Ports Some services are defined with multiple ports as: ```xml ``` And are reported by suds as: ```py url = 'http://www.thomas-bayer.com/axis2/services/BLZService?wsdl' client = Client(url) print client Suds - version: 0.3.3 build: (beta) R397-20081121 Service (BLZService) tns="http://thomas-bayer.com/blz/" Prefixes (1) ns0 = "http://thomas-bayer.com/blz/" Ports (2): (soap) Methods (1): getBank(xs:string blz, ) (soap12) Methods (1): getBank(xs:string blz, ) Types (5): getBankType getBankResponseType getBankType getBankResponseType detailsType ``` This example only has (1) method defined for each port but it could very likely have may methods defined. Suds does not require the method invocation to be qualifed (as shown above) by the port as: ```py client.service..getBank() ``` unless the user wants to specify a particular port. In most cases, the server will work properly with any of the soap ports. However, if you want to invoke the getBank() method on this service the user may qualify the method name with the port. There are (2) ways to do this: - Select a default port using the `port` ` option` before invoking the method as: ```py client.set_options(port='soap') client.service.getBank() ``` - fully qualify the method as: ```py client.service.soap.getBank() ``` **After r551 version 0.3.7, this changes some to support multiple-services within (1) WSDL as follows:** This example only has (1) method defined for each port but it could very likely have may methods defined. Suds does not require the method invocation to be qualifed (as shown above) by the port as: ```py client.service[port].getBank() ``` unless the user wants to specify a particular port. In most cases, the server will work properly with any of the soap ports. However, if you want to invoke the getBank() method on this service the user may qualify the method name with the port. The `port` may be subscripted either by name (string) or index(int). There are many ways to do this: - Select a default port using the `port` ` option` before invoking the method as: ```py client.set_options(port='soap') client.service.getBank() ``` - fully qualify the method using the port `name` as: ```py client.service['soap'].getBank() ``` - fully qualify the method using the port `index` as: ```py client.service[0].getBank() ``` ## WSDL With Multiple Services & Multiple Ports version: 0.3.7+ Some WSDLs define multiple services which may (or may not) be defined with multiple ports as: ```xml ``` And are reported by suds as: ```py url = 'http://www.thomas-bayer.com/axis2/services/BLZService?wsdl' client = Client(url) print client Suds - version: 0.3.7 build: (beta) R550-20090820 Service (BLZService) tns="http://thomas-bayer.com/blz/" Prefixes (1) ns0 = "http://thomas-bayer.com/blz/" Ports (2): (soap) Methods (1): getBank(xs:string blz, ) (soap12) Methods (1): getBank(xs:string blz, ) Types (5): getBankType getBankResponseType getBankType getBankResponseType detailsType Service (OtherBLZService) tns="http://thomas-bayer.com/blz/" Prefixes (1) ns0 = "http://thomas-bayer.com/blz/" Ports (2): (soap) Methods (1): getBank(xs:string blz, ) (soap12) Methods (1): getBank(xs:string blz, ) Types (5): getBankType getBankResponseType getBankType getBankResponseType detailsType ``` This example only has (1) method defined for each port but it could very likely have may methods defined. Suds does __not__ require the method invocation to be qualifed (as shown above) by the service and/or port as: ```py client.service[service][port].getBank() ``` unless the user wants to specify a particular service and/or port. In most cases, the server will work properly with any of the soap ports. However, if you want to invoke the getBank() method on the `OtherBLZService` service the user may qualify the method name with the service and/or port. If not specified, suds will default the service to the 1st server defined in the WSDL and default to the 1st port within each service. Also, when a WSDL defines (1) services, the \[` subscript is applied to the port selection. This may be a little confusing because the syntax for subscripting can seem inconsistent. Both the `service` __and__ `port` may be subscripted either by name (string) or index (int). There are many ways to do this: - Select a default service using the `service` option and default port using `port` option ` option` before invoking the method as: ```py client.set_options(service='OtherBLZService', port='soap') client.service.getBank() ``` - method qualified by `service` and `port` as: ```py client.service['OtherBLZService']['soap'].getBank() ``` - method qualified by `service` and `port` using indexes as: ```py client.service[1][0].getBank() ``` - method qualified by `service` (by name) only as: ```py client.service['OtherBLZService'].getBank() ``` - method qualified by `service` (by index) only as: ```py client.service[1].getBank() ``` Note, that if a WSDL defines more then one service, you __must__ qualify the `service` via `option` or by using the subscripting syntax in order to specify the `port` using the subscript syntax. ## SOAP Headers SOAP headers may be passed during the service invocation by using the `soapheaders` `option` as follows: ```py client = client(url) token = client.factory.create('AuthToken') token.username = 'Elvis' token.password = 'TheKing' client.set_options(soapheaders=token) result = client.service.addPerson(person) ``` OR ```py client = client(url) userid = client.factory.create('Auth.UserID') userid.set('Elvis') password = client.factory.create('Auth.Password') password.set('TheKing') client.set_options(soapheaders=(userid,password)) result = client.service.addPerson(person) ``` OR ```py client = client(url) userid = 'Elmer' passwd = 'Fudd' client.set_options(soapheaders=(userid,password)) result = client.service.addPerson(person) ``` The `soapheaders` option may also be assigned a dictionary for those cases when optional headers are specified and users don\'t want to pass None place holders. This works much like the method parameters. Eg: ```py client = client(url) myheaders = dict(userid='Elmer', passwd='Fudd') client.set_options(soapheaders=myheaders) result = client.service.addPerson(person) ``` Passing `soapheaders` by keyword (dict) is available in 0.3.4 (r442) and later. ## Custom SOAP Headers Custom SOAP headers may be passed during the service invocation by using the `soapheaders` `option`. A `custom` soap header is defined as a header that is required by the service by __not__ defined in the wsdl. Thus, the `easy` method of passing soap headers already described cannot be used. This is done by constructing and passing an `Element` or collection of `Elements` as follows: ```py from suds.sax.element import Element client = client(url) ssnns = ('ssn', 'http://namespaces/sessionid') ssn = Element('SessionID', ns=ssnns).setText('123') client.set_options(soapheaders=ssn) result = client.service.addPerson(person) ``` Do __not__ try to pass the header as an XML `string` such as: ```py client = client(url) ssn = '123' client.set_options(soapheaders=ssn) result = client.service.addPerson(person) ``` It will not work because: 1. Only `Elements` are processed as `custom` headers. 1. The XML string would be escaped as <ssn:SessionID>123</ssn:SessionID> anyway. \*Notes: 1. Passing single `Elements` as soap headers fixed in Ticket \#232 (r533) and will be released on 0.3.7. 1. Reusing this `Element` in subsequent calls fixed in Ticket \#233 (r533) and will be released on 0.3.7. ## WS-SECURITY As of r452 / 0.3.4 (beta) to provide basic ws-security with `UsernameToken` with `clear-text` password (no digest). ```py from suds.wsse import * security = Security() token = UsernameToken('myusername', 'mypassword') security.tokens.append(token) client.set_options(wsse=security) ``` or, if the `Nonce` and `Create` elements are needed, they can be generated and set as follows: ```py from suds.wsse import * security = Security() token = UsernameToken('myusername', 'mypassword') token.setnonce() token.setcreated() token.setnonceencoding(True) token.setpassworddigest('digest') security.tokens.append(token) client.set_options(wsse=security) ``` but, if you want to manually set the `Nonce` and/or `Created`, you may do as follows: ```py from suds.wsse import * security = Security() token = UsernameToken('myusername', 'mypassword') token.setnonce('MyNonceString...') token.setcreated(datetime.now()) security.tokens.append(token) client.set_options(wsse=security) ``` ## Multi-document (Document/Literal) In most cases, services defined using the document/literal SOAP binding style will define a single document as the message payload. The \ will only have (1) \ which references an \ in the schema. In this case, suds presents a RPC view of that method by displaying the method signature as the contents (nodes) of the document. Eg: ```xml ... ... ... ... ``` Suds will report the method `foo` signature as: foo(xs:string name, xs:int age,) This provides an RPC feel to the document/literal soap binding style. Now, if the wsdl defines: ```xml ... ... ... ... ``` Suds will be forced to report the method `foo` signature as: foo(Foo foo, xs:int bar) The message has (2) parts which defines that the message payload contains (2) documents. In this case, suds must present a /Document/ view of the method. ## HTTP Authentication #### Basic As of version 0.3.3 and newer, `basic` HTTP authentication as defined by [RFC-2617](http://www.ietf.org/rfc/rfc2617.txt) can be done as follows: ```py client = Client(url, username='elmer', password='fudd') ``` Authentication is provided by the (default) `HttpAuthenticated` `Transport` class defined in the `transport.https` module that follows the challenge (http 401) / response model defined in the RFC. As of r537, `0.3.7` beta, a new `Transport` was added in the `transport.http` module that provides http authentication for servers that don\'t follow the challenge/response model. Rather, it sets the `Authentication:` http header on __all__ http requests. This transport can be used as follows: ```py from suds.transport.http import HttpAuthenticated t = HttpAuthenticated(username='elmer', password='fudd') client = Client(url, transport=t) ``` Or ```py from suds.transport.http import HttpAuthenticated t = HttpAuthenticated() client = Client(url, transport=t, username='elmer', password='fudd') ``` For version: 0.3.3 and older ONLY: Revision 63+ (and release 0.1.8+) includes the migration from httplib to urllib2 in the suds default `transport` which enables users to leverage all of the authentication features provided by urllib2. For example basic HTTP authentication could be implemented as follows: ```py myurl = 'http://localhost:7080/webservices/WebServiceTestBean?wsdl' client = Client(myurl) import urllib2 baseurl = 'http://localhost:7080/' username = 'myuser' password = 'mypassword' passman = urllib2.HTTPPasswordMgrWithDefaultRealm() passman.add_password(None, baseurl, username, password) authhandler = urllib2.HTTPBasicAuthHandler(passman) client.options.transport.urlopener = urllib2.build_opener(authhandler) ``` The suds default `HTTP transport` uses urllib2.urlopen(), basic http authentication is handled automatically if you create the transport\'s urlopener correctly and set the urlopener. #### Windows (NTLM) As of 0.3.8, suds includes a `NTLM transport` based on urllib2. This implementation requires `users to install the [python-ntlm](http://code.google.com/p/python-ntlm/). It is __not__ packaged with suds. To use this, simply do something like: ```py from suds.transport.https import WindowsHttpAuthenticated ntlm = WindowsHttpAuthenticated(username='xx', password='xx') client = Client(url, transport=ntlm) ``` ## Proxies The suds default `transport` handles proxies using urllib2.Request.set\_proxy(). The proxy options can be passed set using Client.set\_options. The proxy options must contain a dictionary where keys=protocols and values are the hostname (or IP) and port of the proxy. ```py ... d = dict(http='host:80', https='host:443', ...) client.set_options(proxy=d) ... ``` ## Message Injection (Diagnostics/Testing) The service API provides for message/reply injection. To inject either a soap message to be sent or to inject a reply or fault to be processed as if returned by the soap server, simply specify the `__inject` keyword argument with a value of a dictionary containing either: - msg = \ - reply = \ - fault = \ when invoking the service. Eg: Sending a raw soap message: ```py message = \ """ ... """ print client.service.test(__inject={'msg':message}) ``` Injecting a response for testing: ```py reply = \ """ ... """ print client.service.test(__inject={'reply':reply}) ``` ## SSL certificate verification & Custom Certificates With Python 2.7.9, SSL/TLS verification is turned on by default. This can be a problem when suds is used against an endpoint which has a self-signed certificate, which is quite common in the corporate intranet world. One approach to turn off certificate validation in suds is to use a custom transport class. For example in Python 3: ``` import urllib.request import ssl import suds.transport.http class UnverifiedHttpsTransport(suds.transport.http.HttpTransport): def __init__(self, *args, **kwargs): super(UnverifiedHttpsTransport, self).__init__(*args, **kwargs) def u2handlers(self): handlers = super(UnverifiedHttpsTransport, self).u2handlers() context = ssl.create_default_context() context.check_hostname = False context.verify_mode = ssl.CERT_NONE handlers.append(urllib.request.HTTPSHandler(context=context)) return handlers client = Client(url, transport=UnverifiedHttpsTransport()) ``` In addition, if a custom set of certificates and/or root CA is needed, this can also be done via a custom transport class. For example, in Python 3: ``` class ClientHttpsTransport(HttpTransport): def __init__(self, certfile, keyfile, cafile, *args, **kwargs): super(ClientHttpsTransport, self).__init__(*args, **kwargs) self.certfile = certfile self.keyfile = keyfile self.cafile = cafile def u2handlers(self): handlers = super(ClientHttpsTransport, self).u2handlers() context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=self.cafile) context.load_cert_chain(self.certfile, self.keyfile) context.check_hostname = False context.verify_mode = ssl.CERT_NONE handlers.append(urllib.request.HTTPSHandler(context=context)) return handlers custom_https = ClientHttpsTransport('/path/to/certificate_file', '/path/to/key_file', '/path/to/ca_file') client = Client(url, transport=custom_https), ``` ## Timeouts Per request timeouts can be set by using a `__timeout` keyword argument in each call. This supersedes the global client default. For example, the following call will have a timeout of 10 seconds: ```python client = Client(url, timeout=30) client.service.test(__timeout=10) ``` ## Performance As of 0.3.5 r473, suds provides some URL caching. By default, http get(s) such as getting the WSDL and importing XSDs are cached. The caching applies to URL such as those used to get the referenced WSDLs and XSD schemas but does __not__ apply to service method invocation as this would not make sense. In 0.3.9, `FileCache` was replaced with `ObjectCache`. The default cache is a `ObjectCache` with an expiration of (1) day. This duration may be adjusted as follows: ```py cache = client.options.cache cache.setduration(days=10) ``` OR ```py cache.setduration(seconds=90) ``` The duration my be (months, weeks, days, hours, seconds ). The default location (directory) is /tmp/suds so Windows users will need to set the location to something that makes sense on windows. The cache is an `option` and can be set with any kind of `Cache` object or may be disabled by setting the option to None. So, uses may plug-in any kind of cache they want. ```py from suds.cache import Cache class MyCache(Cache) ... client.set_options(cache=MyCache()) ``` To disable caching: ```py client.set_options(cache=None) ``` ## Fixing Broken Schema(s) There are many cases where the schema(s) defined both within the WSDL or imported are broken. The most common problem is failure to import the follow proper import rules. That is, references are made in one schema to named objects defined in another schema without importing it. The `doctor` module defines a set of classes for mending broken schema(s). ### Doctors The `Doctor` class provides the interface for classes that provide this service. Once defined, the doctor can be specified using the schema doctor as an `option` when creating the Client. Or, you can use one of the stock doctors - ` ImportDoctor` - Used to fix import problems. For example: ```py imp = Import('http://schemas.xmlsoap.org/soap/encoding/') imp.filter.add('http://some/namespace/A') imp.filter.add('http://some/namespace/B') doctor = ImportDoctor(imp) client = Client(url, doctor=doctor) ``` In this example, we\'ve specified that the doctor should examine schema(s) with a targetNamespace of `http://some/namespace/A or http://some/namespace/B` and ensure that the schema for the `http://schemas.xmlsoap.org/soap/encoding/` is imported. If those schema(s) do not have an \ for those namespaces, it is added. For cases where the schemaLocation is not bound to the namespace, the `Import` can be created specifying the location has follows: ```py imp = Import('http://www.w3.org/2001/XMLSchema', location='http://www.w3.org/2001/XMLSchema.xsd') imp.filter.add('http://some/namespace/A') imp.filter.add('http://some/namespace/B') doctor = ImportDoctor(imp) client = Client(url, doctor=doctor) ``` A commonly referenced schema (that is not imported) is the SOAP section 5 encoding schema. This can now be fixed as follows: ```py imp = Import('http://schemas.xmlsoap.org/soap/encoding/') imp.filter.add('http://some/namespace/A') doctor = ImportDoctor(imp) client = Client(url, doctor=doctor) ``` note: Available in r512+ and 0.3.6 `beta`. ### Binding Schema Locations (URL) to Namespaces Some WSDL(s) schemas import as: \\> without schemaLocation=\"\" and expect processor to use the namespace URI as the schema location for the namespace. The specifications for processing \ leave the resolution of the imported namespace to a schema to the descession of the processor (in this case suds) when \@schemaLocation is not specified. Suds always looks within the WSDL for a schema but does not look outside unless: - A schemaLocation is specified, or - A static binding is specified using the following syntax: ```py from suds.xsd.sxbasic import Import ns = 'http://schemas.xmlsoap.org/soap/encoding/' location = 'http://schemas.xmlsoap.org/soap/encoding/' Import.bind(ns, location) ``` Or, the shorthand (when location is the same as the namespace URI) ```py Import.bind(ns) ``` note: `http://schemas.xmlsoap.org/soap/encoding/'` automatically `bound` in 0.3.4 as of (r420). ## Plugins New in 0.4 is a plugin facility. It is intended to be a general, more extensible, mechanism for users to inspect/modify suds while it is running. Today, there are two `one-off` ways to do this: 1\. bindings.Binding.replyfilter - The reply text can be inspected & modified. 2. xsd.Doctor - The doctor `option` used to mend broken schemas. The `plugin` module provides a number of classes but users really only need to be concerned with a few: - The `Plugin` class which defines the interface for user plugins - The `Context` classes which are passed to the plugin. The plugins are divided into (4) classes based on the `tasks` of the soap client: `Initialization` :: The client initialization task which is when the client has digested the WSDL and associated XSD. `Document Loading` :: The document loading task. This is when the client is loading WSDL & XSD documents. `Messaging` :: The messaging task is when the client is doing soap messaging as part of method (operation) invocation. #### InitPlugin The `InitPlugin` currently has (1) hook: `initialized()` :: Called after the client is initialized. The context contains the `WSDL` object. #### DocumentPlugin The `DocumentPlugin` currently has (2) hooks:: `loaded()` :: Called before parsing a `WSDL` or `XSD` document. The context contains the url & document text. `parsed()` :: Called after parsing a `WSDL` or `XSD` document. The context contains the url & document `root`. #### MessagePlugin The `MessagePlugin` currently has (5) hooks :: *marshalled():: Provides the plugin with the opportunity to inspect/modify the envelope Document __before__ it is sent. * sending():: Provides the plugin with the opportunity to inspect/modify the message text __before__ it is sent. * received():: Provides the plugin with the opportunity to inspect/modify the received XML text __before__ it is SAX parsed. * parsed():: Provides the plugin with the opportunity to inspect/modify the sax parsed DOM tree for the reply __before__ it is unmarshalled. * unmarshalled():: Provides the plugin with the opportunity to inspect/modify the unmarshalled reply __before__ it is returned to the caller. General usage: ```py from suds.plugin import * class MyPlugin(DocumentPlugin): ... plugin = MyPlugin() client = Client(url, plugins=[plugin]) ``` Plugins need to override __only__ those methods (hooks) of interest - not all of them. Exceptions are caught and logged. Here is an example. Say I want to add some attributes to the document root element in the soap envelope. Currently suds does not provide a way to do this using the main API. Using a plugin much like the schema doctor, we can do this. Say our envelope is being generated by suds as: Elmer Fudd 55 But what you need is: Elmer Fudd 55 ```py from suds.plugin import MessagePlugin class MyPlugin(MessagePlugin): def marshalled(self, context): body = context.envelope.getChild('Body') foo = body[0] foo.set('id', '12345') foo.set('version', '2.0') client = Client(url, plugins=[MyPlugin()]) ``` In the future, the `Binding.replyfilter` and `doctor` __option__ will likely be deprecated. The `ImportDoctor` has been extended to implement the `Plugin`.onLoad() API. In doing this, we can treat the `ImportDoctor` as a plugin: ```py imp = Import('http://www.w3.org/2001/XMLSchema') imp.filter.add('http://webservices.serviceU.com/') d = ImportDoctor(imp) client = Client(url, plugins=[d]) ``` We can also replace our Binding.replyfilter() with a plugin as follows: ```py def myfilter(reply): return reply[1:] Binding.replyfilter = myfilter # replace with: class Filter(MessagePlugin): def received(self, context): reply = context.reply context.reply = reply[1:] client = Client(url, plugins=[Filter()]) ``` ## Technical (FYI) Notes - XML namespaces are represented as a tuple (prefix, URI). The default namespace is (None,None). - The suds.sax module was written becuase elementtree and other python XML packages either: have a DOM API which is very unfriendly or: (in the case of elementtree) do not deal with namespaces and especially prefixes sufficiently. - A qualified reference is a type that is referenced in the WSDL such as \ where the qualified reference is a tuple (\'Person\', (\'tns\',\'\')) where the namespace is the 2nd part of the tuple. When a prefix is not supplied as in \, the namespace is the targetNamespace for the defining fragment. This ensures that all lookups and comparisons are fully qualified. suds-1.1.2/TODO.txt000066400000000000000000003465151425611400200140100ustar00rootroot00000000000000PRIORITIZED: ================================================= (21.12.2011.) (+) * (Jurko) Prepare for the initial forked project release. (+) * Add todo list. (+) * Document how to access this forked project's development sources & (+) released files. (+) * Jurko's Mercurial repository hosted at BitBucket and accessible (+) from 'http://bitbucket.org/jurko/suds'. (+) * Already documented in README. (+) * Add more details to HACKING. (+) * Suds library Python 3 patches hosted in a Mercurial patch queue (+) repository at BitBucket and accessible from (+) 'http://bitbucket.org/bernh/suds-python-3-patches'. (+) * Already documented in HACKING. (+) * (Jurko) Minor stylistic changes & typo corrections. (+) * Code. (+) * 'tranparent' --> 'transparent'. (+) * 'if tns' --> 'if tns'. (+) * 'docuemnt' --> 'document'. (+) * '('restriction', 'any', 'list',)' --> '('restriction', 'any', (+) 'list')'. (+) * And other unnecessary trailing tuple commas. (+) * 'Qualfied' --> 'Qualified'. (+) * 'Resolveds' --> 'Resolves'. (+) * 'describe a port and it's list of methods' --> 'describe a port (+) and its list of methods'. (+) * 'dependancies' --> 'dependencies'. (+) * 'imcoming' --> 'incoming'. (+) * 'relavent' --> 'relevant'. (+) * 'inidcat' --> 'indicat'. (22.12.2011.) (+) * (Jurko) Prepare for the initial forked project release. (+) * Rename top level project documentation files to use the .txt extension (+) to make them friendlier to Windows users. (+) * Research release procedure. (+) * Open PyPI account. (+) * How to prepare a source distribution package. (+) * Change author information. (+) * Include tests. (+) * Include all the top-level documentation files. (+) * 'README'. (+) * 'LICENSE'. (+) * 'HACKING'. (+) * 'TODO'. (+) * Note the original project author in the package description. (+) * Include correct license information. (+) * See what the difference between author and maintainer (+) information is and where it can be seen. (+) * Try using 'setuptools_hg' to simplify specifying the project (+) sources. (+) * Failed when used under Python 3. (+) * How to upload the prepared distribution packages. (+) * Should upload a source distribution only. (23.12.2011.) (+) * (Jurko) Prepare for the initial forked project release. (+) * Research release procedure. (+) * How to upload the prepared distribution packages. (+) * PyPI. (24.12.2011.) (+) * (Jurko) Prepare for the initial forked project release. (+) * Research release procedure. (+) * How to upload the prepared distribution packages. (+) * BitBucket. (+) * Document the project's official download URL. (+) * Document how to access this forked project's development sources & (+) released files. (+) * Released project packages accessible from PyPI & BitBucket. (+) * Installing the project using distribute or pip. (+) * Document release procedure. (+) * Version identification. (+) * Remove the '(development)' suffix for official release builds. (+) * Format ' jurko #', e.g. '0.4.1 jurko 1'. (+) * Tag in Hg. (+) * Name the tag like 'release-', e.g. (+) 'release-0.4.1 jurko 1'. (+) * Prepare official releases based only on tagged commits. (+) * Prepare source distribution package, register the new release (+) at PyPI and upload the prepared source package. (+) * Run 'setup.py sdist register upload'. (+) * Upload the prepared source package to the project site. (+) * Archive the prepared source release locally if needed. (+) * Next development version identification. (+) * Bump up the forked project version counter. (+) * Add back the '(development)' suffix. (+) * Commit all local changes. (+) * (Jurko) Constructing a SOAP request containing data stored in a sequence (+) inside a choice. (+) * Test scenario (syntax not precise). (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) * When 's' is None and 'a' is not - 'a' should be used. (+) * When 'a' is None and 's' is not - 's' should be used. (+) * When 's' is used, all of its child elements should be used independent (+) of whether they are None or not. (+) * Add related test. (+) * (Jurko) Prepare the '0.4.1 jurko 1' release. (+) * Follow the documented release procedure. (+) * Update version information. (+) * Tag in Hg. (+) * Upload the source package. (+) * Project site. (+) * PyPI. (+) * (Jurko) Fix getting a suds.client object's string representation when the (+) client is initialized with the following WSDL. Calling 'str(client)' (+) reports 'IndexError: list index out of range'. (+) * WSDL. (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) (+) * Research. (+) * Caused by undocumented suds.client.Client behaviour - it was (+) expecting a specifically formatted suds.__build__ string which was (+) not the case with the forked project release. (+) * Add a test. (+) * Fix. (+) * Plan preparing a patched release. (+) * (Jurko) Prepare the '0.4.1 jurko 2' release. (+) * Update release notes. (+) * Follow the documented release procedure. (+) * Update version information. (+) * Tag in Hg. (+) * Upload the source package. (+) * Project site. (+) * PyPI. (25.12.2011.) (+) * (Jurko) Printing out a list of function parameters should not print an (+) additional trailing comma after the last parameter. (+) * Research. (+) * suds.servicedefinition.ServiceDescription.description() code needs (+) to be changed. (+) * Prepare test. (+) * Update code. (+) * (Jurko) 'suds.xsd.xsbasic.Enumeration' objects should list their value in (+) their string representation. (+) * Research. (+) * Prepare test. (+) * Update code. (+) * (Jurko) 'suds.sudsobject.Metadata' __unicode__()/__str__()/__repr__() (+) functions should not raise an AttributeError. (+) * Research. (+) * There should be no need to access a 'suds.sudsobject.Metadata' (+) object's __metadata__ member as done for 'suds.sudsobjects.Facade' (+) class instances. (+) * Prepare test. (+) * Update code. (26.12.2011.) (+) * (Jurko) Clean up suds.xsd.sxbasic.TypedContent.resolve(). (+) * Research. (+) * Base class resolve() behaviour. (+) * Other resolve() functions in suds. (+) * 'resolve()' related caching. (+) * Clean up the SchemaObject resolve() implementations. (+) * Caching not needed in TypedContent base classes. (+) * Document. (+) * Returns the same XSD node when the node does not have an (+) explicitly specified external type. (+) * When called for an XSD node with an explicitly specified external (+) type returns that type's XSD node. (+) * (Jurko) Clean up suds.xsd.sxbasic.TypedContent.resolve(). (+) * Research WSDL structure related to the resolving type references. (+) * 'type'. (+) * 'ref'. (+) * Prepare additional resolve() tests. (+) * 'ref'. (+) * Valid. (+) * Recursive. (+) * Invalid. (+) * References to nodes referencing other nodes. (+) * There seems to be no way to do this in WSDL so seems no (+) reason to keep the complicated and potentially buggy (+) recursive resolve() implementation. (+) * Refactor the resolve() implementation to remove recursion. (+) * Todo items obsoleted by this refactoring. (+) * Prevent possible endless resolve() loops due to resolve() (+) directly or indirectly returning the same TypedContent (+) instance. (+) * Refactor to cache the final resolved type instead of a possibly only (+) partially resolved one when resolving without allowing resolving to (+) builtin types. (+) * Research. (+) * Prepare test. (+) * Update code. (+) * (Jurko) Check and remove detected potential unused imports if they are no (+) longer needed. (+) * splitPrefix. (+) * DefinitionsReader. (+) * (Jurko) Prepare the '0.4.1 jurko 3' release. (+) * Update release notes. (+) * Follow the documented release procedure. (+) * Update version information. (+) * Tag in Hg. (+) * Upload the source package. (+) * Project site. (+) * PyPI. (+) * (Jurko) Look into suds.xsd.sxbase.SchemaObject.unbounded(). It seems to (+) return True even when the object is bounded with a max value greater than (+) 1. (+) * Research. (+) * Add tests. (+) * 'min'. (+) * 'max'. (+) * 'optional'. (+) * 'required'. (+) * 'unbounded'. (+) * Update code - rename unbounded to multi_occurrence. (27.12.2011.) (+) * (Jurko) Get calling a web service operation taking no parameters to work (+) correctly. (+) * Research. (+) * Seems to work fine. The original problem triggering this task (+) seems to have been caused by an invalid WSDL. (+) * Add a task to add more detailed test cases for this. (17.04.2012.) (+) * (Jurko) Merge upstream changes from the original suds development (+) repository. (+) * (Jurko) Update embedded author values so they do not include non-ASCII (+) characters causing problems with the 'distribute' based setup procedure (+) which erroneously assumes they have been prepared using the user's local (+) code-page. (+) * (Jurko) Process received pull requests on BitBucket. (+) * (Jurko) Prepare the '0.4.1 jurko 4' release. (+) * Update release notes. (+) * Follow the documented release procedure. (+) * Update version information. (+) * Tag in Hg. (+) * Upload the source package. (+) * Project site. (+) * PyPI. (28.02.2013.) (+) * (Jurko) Merge changes prepared by Juraj Ivančić. (+) * Update the original Hg repository containing Python 3 related fixes. (+) * (Jurko) Process received pull requests. (01.03.2013.) (+) * (Jurko) Sync with external related repositories. (+) * 'http://bitbucket.org/palday/suds'. (27.03.2013.) (+) * (Jurko) Fix buggy Python 3 support patch related to reading the cache (+) version. (+) * The cache version file should be read as a text and not as a binary (+) file. (+) * (Jurko) Fix test_enumeration_type_string_should_contain_its_value test (+) under Python 2. (+) * (Jurko) Test & fix Python 2.4 compatibility. (+) * (Jurko) Fix input/output binding usage. (+) * Incorrect binding was being used in several places. (+) * Some of the uses related to processing the SOAP Fault error reporting (+) element seem to be 'fake', needed only because of a a bit messy (+) design. Planned to be fixed soon. (28.03.2013.) (+) * (Jurko) Add web service reply processing related unit tests. (+) * (Jurko) Remove undocumented, untested & unused binding.replyfilter (+) functionality. (+) * (Jurko) Remove seeming unused SoapClient last_sent() and last_received() (+) functionality. (+) * (Jurko) Add a test for unicode Fault data processing. (+) * (Jurko) Merge SoapClient failed() & succeeded() functions into the same (+) process_reply() function. (+) * (Jurko) Make binding classes no longer have anything to do with method (+) independent Fault element processing. (+) * (Jurko) Make reply XML processing check the namespace used for Envelope & (+) Body elements. (+) * (Jurko) Make SOAP Fault processing check the namespaces used for all (+) relevant tags. (+) * (Jurko) Make HTTP status code 200 XML replies containing a Fault element (+) consistently reported as SOAP faults (plus issue a warning about the (+) non-standard HTTP status code) both when reporting such faults using (+) exceptions or by returning a (status, reason) tuple. (+) * Currently this is done only when reporting them using exceptions. (+) * (Jurko) Make plugins received() & parsed() calls now process both success (+) & error replies. (+) * (Jurko) SOAP fault reports with invalid Fault structure should not cause (+) suds code to break with an 'invalid attribute' exception. (+) * (Jurko) SOAP fault reports with no tag (optional) should not (+) cause suds code to break with an 'invalid attribute' exception when run (+) with the suds 'faults' option set to false. (+) * (Jurko) Clean up message reply processing return codes with suds 'faults' (+) option set to both true & false. (+) * (Jurko) Reorganize SimClient injection keywords. (+) * 'msg' - request message. (+) * 'reply' - reply message ('msg' must not be set). (+) * 'status' - HTTP status code accompanying the 'reply' message. (+) * 'description' - description string accompanying the 'reply' message. (+) * (Jurko) Check failing tests. (+) * All tests now pass except for ones related to SOAP Fault unicode (+) faultstring processing. (29.03.2013.) (+) * (Jurko) Sync with external related repositories. (+) * 'http://bitbucket.org/blarghmatey/suds-blarghmatey'. (+) * (Jurko) Additional SOAP web service reply tests. (+) * (Jurko) Fix detected unicode problems. (+) * Remove invalid WebFault fix merged from an external source. (+) * All suds exception classes now contain unicode description messages. (+) * Undo a hasty unicode related WebFault fix merged from an external (+) source in revision 16b084e8eea6511981d171e63cada98b58720c38. (+) * Rename smart_str class to byte_str and make it accept only string (+) parameters. (+) * Clean Python2/3 compatibility DocumentStore fix. (+) * Now contains raw data instead of unicode strings. (+) * This also fixes a problem where unicode data read from the (+) DocumentStore would fail to be encoded using the default encoding, (+) which would then get reported as 'document not found'. (+) * SAX parser now accepts only byte string content instead of also (+) accepting unicode strings containing latin1 characters only. (+) * Make tests now specify their fixed WSDL & reply content as byte (+) strings only. (+) * Make all tests pass. (+) * Python 2.4. (+) * Python 2.7.3. (+) * Python 3.2.3. (+) * (Jurko) Remove Python 2/3 unicode encoding compatibility support assuming (+) that its encoded unicode representations contain only latin1 characters. (+) * SoapClient 'location' cleanup. (+) * Should be stored as a unicode object instead of being converted (+) from its byte representation assuming it was encoded using a (+) specific encoding, e.g. if read from a WSDL schema, it should be (+) decoded the same as the rest of the WSDL schema. (+) * Remove str2bytes() & bytes2str(). (+) * (Jurko) Remove the str_to_utf8_in_py2() Python 2/3 unicode encoding (+) compatibility support function as it no longer seems to be needed. (+) * (Jurko) Add tests for web service operation input & output element types. (30.03.2013.) (+) * (Jurko) Improve suds tests. (31.03.2013.) (+) * (Jurko) Add tests for wrapped suds operation input & output data. (01.04.2013.) (+) * (Jurko) Add tests for wrapped suds operation output data. (+) * (Jurko) Merge patches sent in by Juraj Ivančić from PKE sistemi. (+) * Add tests for disabled wrapped suds operation input & output data (+) support. (+) * Add code for disabling suds library wrapped parameter support. (02.04.2013.) (+) * (Jurko) Restriction support cleanup based on patches sent in by Juraj (+) Ivančić from PKE sistemi. (+) * Research. (+) * Not enough time to research this thoroughly and come up with a (+) complete and well tested solution. (+) * Prepare and commit related tests. (+) * Mark the tests as 'expected to fail' & comment the reasons. (+) * Commit as a separate unfinished private branch. (+) * (Jurko) Prepare the '0.4.1 jurko 5' release. (+) * Update release notes. (08.05.2013.) (+) * (Jurko) Make suds construct SOAP requests with correct element namespaces (+) when their XSD schema definition nodes reference other nodes with a (+) different namespace. (+) * Research. (+) * Add test. (+) * Implement. (+) * Report back to Jens Arm from KabelDeutschland who reported the issue. (+) * (Jurko) Support specifying a custom DocumentStore instance for a specific (+) Client. (+) * Support. (+) * Update test code adding documents to the global DocumentStore instance (+) to use a local one instead. (+) * Cleanup. (+) * DocumentStore.open() can return the bytes object directly instead (+) of having to wrap it inside a BytesIO instance. (+) * Remove unnecessary Cache functions. (+) * getf() should be left in the FileCache class only. (+) * putf() should be removed completely. (+) * Add tests. (+) * Separate DocumentStore instances must not share content. (+) * Not specifying a DocumentStore instance uses the default global (+) one. (+) * Default content. (+) * Updating content. (+) * Accessing existing content. (+) * Accessing missing content. (17.06.2013.) (+) * (Jurko) Upgrade the setup procedure to use the latest setuptools 0.7.2 (+) release instead of the now deprecated 'distribute' Python package. (+) * Research. (+) * Implement. (18.06.2013.) (+) * (Jurko) Upgrade the setup procedure to use the latest setuptools 0.7.2 (+) release instead of the now deprecated 'distribute' Python package. (+) * Add automated setuptools installation (downloaded on-demand from (+) PyPI). (+) * Fix issues with installing on Python 2.4. (+) * Add project installation troubleshooting notes to the main readme. (+) * (Jurko) See how to allow using the setup script's 'test' option to run the (+) project's pytest based test suite. (19.06.2013.) (+) * (Jurko) Resolve test failures caused by suds generating slightly different (+) SOAP requests when using Python 3.3. (+) * Tests should not depend on the order in which XML attributes are (+) specified for a single XML element where this is not necessary. (11.11.2013.) (+) * (Jurko) Prepare the '0.4.1 jurko 5' release. (+) * Follow the documented release procedure. (+) * Update release notes. (+) * Update version information. (+) * Tag in Hg. (+) * Upload the source package. (+) * Project site. (+) * PyPI. (18.11.2013.) (+) * (Jurko) Fix suds time-zone handling according to a pull request received (+) on BitBucket from MDuggan1. (+) * Research. (+) * Suds assumes that all timezones have a full-hour offset from the (+) UTC timezone and does not work correctly with those that do not. (+) * This seems to be a suds specific problem and not a more (+) general Python issue as some information on the net implies. (+) * FixedOffsetTimezone. (+) * datetime.tzinfo subclass. (+) * Used only in test code. (+) * Represents fixed offset timezones with no daylight saving (+) time. (+) * Start morphing the current suds & test code base towards the suggested (+) patched code. (19.11.2013.) (+) * (Jurko) Fix suds time-zone handling according to a pull request received (+) on BitBucket from MDuggan1. (+) * Research. (+) * Prepare date/time string parsing tests. (20.11.2013.) (+) * (Jurko) Start documenting the upcoming suds 0.4.1 jurko 6 release. (+) * (Jurko) Fix suds time-zone handling according to a pull request received (+) on BitBucket from MDuggan1. (+) * Research. (+) * Implement parsing. (+) * DateTime no longer derived from Date & Time. (+) * Date constructed from datetime.datetime should hold a datetime.date. (+) * Research. (+) * See if sax.date.Date("1900-01-01+02:00") should hold a timezone (+) aware date object. (+) * Related test: TestDate.testStringToValue(). (+) * Timezone data. (+) * See when we need to specify timezone information. (+) * In tests when we create DateTime/Time objects. (+) * Default timezone when parsing web service responses. (+) * YAGNI - for now left to user code reading specific (+) DateTime/Time objects. (+) * Default timezone when constructing web service requests. (+) * YAGNI - for now left to user code creating specific (+) DateTime/Time objects. (+) * Implement. (+) * Contained datetime.DateTime/Time objects should hold their (+) timezone information. (+) * Research. (+) * Test TestDate.testStringToValue_failure() fails on Python 2 but (+) passed on Python 3. (+) * Fixed by the latest implementation changes. (21.11.2013.) (+) * (Jurko) Fix suds time-zone handling according to a pull request received (+) on BitBucket from MDuggan1. (+) * Check for feedback from users requesting this patch. (+) * Add tests. (+) * FixedOffsetTimezone class. (+) * Fix FixedOffsetTimezone.tzname() output string formatting bug with (+) negative timezone offsets. (+) * Add tests. (+) * UtcTimezone class. (+) * (Jurko) Remove support for timezone specifiers including seconds as such (+) are not supported by either Python or the XSD data types specification. (+) * Add/update tests. (+) * Input like "+10:10:10" should be rejected. (+) * Timezone offset timedelta objects containing more detailed than (+) minute information. (+) * Remove support. (+) * (Jurko) Add tests making sure timezone indicator strings without a colon (+) between hours and minutes are not accepted. (+) * This leads to border cases like "+121" where you do not know whether (+) this represents "+01:21" or "+12:01". (22.11.2013.) (+) * (Jurko) Remove date/time related test code duplication. (+) * Date, DateTime & Time classes. (+) * Clean up test function names. (+) * Test construction from unexpected objects. (+) * Test construction from datetime.date/datetime/time objects. (+) * str() tests. (+) * XDate & Date class. (+) * XDateTime & DateTime class. (+) * XTime & Time class. (+) * Timezone handling checks in XDateTime & XTime classes. (23.11.2013.) (+) * (Jurko) Make converting datetime/time to string output subsecond (+) information without trailing zeroes. (+) * Test. (+) * Implement. (+) * Discard as the implementation complexity seems way too great and the (+) gain does not seem to reciprocate the cost. (+) * (Jurko) Update the project's versioning scheme to no longer have pip (+) detect suds-jurko releases as 'prerelease' only due to our version tag (+) formatting. (+) * Accept that the original suds project has died and continue with the (+) natural version number progression. (+) * (Jurko) Plan a new release. (+) * (Jurko) Update used setuptools version. (+) * (Jurko) Remove unused project files inherited from the original suds (+) project. (25.11.2013.) (+) * (Jurko) Test the project with different Python installations. (+) * Python 2.4.3/x86, on Windows 7/SP1/x64. (+) * Install. (+) * 'setuptools'. (+) * 'pip'. (+) * Describe encountered problems in 'HACKING.txt'. (+) * 'pytest'. (+) * Describe encountered problems in 'HACKING.txt'. (+) * Run tests. (+) * Python 2.4.4/x86, on Windows 7/SP1/x64. (+) * Install. (+) * 'setuptools'. (+) * 'pip'. (+) * Describe encountered problems in 'HACKING.txt'. (+) * 'pytest'. (+) * Describe encountered problems in 'HACKING.txt'. (+) * Run tests. (+) * Python 2.7.6/x64, on Windows 7/SP1/x64. (+) * Install. (+) * 'setuptools'. (+) * 'pip'. (+) * 'pytest'. (+) * Run tests. (+) * Python 3.2.5/x64, on Windows 7/SP1/x64. (+) * Install. (+) * 'setuptools'. (+) * 'pip'. (+) * 'pytest'. (+) * Run tests. (+) * Python 3.3.3/x86, on Windows 7/SP1/x64. (+) * Install. (+) * 'setuptools'. (+) * 'pip'. (+) * 'pytest'. (+) * Run tests. (+) * Python 3.3.3/x64, on Windows 7/SP1/x64. (+) * Install. (+) * 'setuptools'. (+) * 'pip'. (+) * 'pytest'. (+) * Run tests. (+) * (Jurko) Document the test environment setup in HACKING.txt. (+) * (Jurko) Prepare a new suds-jurko 0.5 release. (28.11.2013.) (+) * (Jurko) Look into a reported problem with how unicode data gets encoded (+) inside a suds SOAP request with Python 2. Reported by Alexey Sveshnikov (+) and mduggan1. (+) * Get a reproducible use case from Alexey Sveshnikov. (+) * Research. (+) * Data in HTTP requests needs to be encoded as defined by the (+) Content-Type header given in that request (ISO-8859-1 being the (+) default). (+) * Suds set the "Content-Type=text/xml; charset=utf-8" HTTP request (+) header for all of its SOAP requests. (+) * Python's http module (used internally by the urllib module) add (+) the given message data to its existing data. Suds gives its (+) message data to urllib as an utf-8 encoded bytes object. If (+) existing message data is unicode, it will attempt to forcefully (+) convert the given message data to unicode assuming all it contains (+) is ASCII characters. (+) * Suds message data is already a utf-8 encoded bytes object. (+) * With Python-3 httplib's previous message data is a bytes and not a (+) string object. (+) * The reason why httplib's message content is converted to unicode (+) is that with Python 2 the initial header is a unicode object (+) u'POST /service HTTP/1.1' while with Python 3 it is a bytes object (+) b'POST /service HTTP/1.1'. (+) * Python 2. (+) * Affects Python 2.7. (+) * Does not affect Python 2.4. (+) * Python 3. (+) * Its httplib Request object automatically converts the (+) passed URL to a bytes object (assumes it contains only (+) ASCII characters) which then prevents all the other (+) request data from being forcibly converted to unicode. (29.11.2013.) (+) * (Jurko) Look into a reported problem with how unicode data gets encoded (+) inside a suds SOAP request with Python 2. Reported by Alexey Sveshnikov (+) and mduggan1. (+) * Reduce the reproducible use case. (+) * Should not require an external web service. (+) * Integrate into regular suds-jurko tests. (+) * Make the reproducible test case not attempt to connect to the network (+) if the test passes. (+) * Fix the issue. (+) * Confirm with Alexey Sveshnikov that it is ok with him to make the (+) reproducible use case public. (+) * Close related project pull requests on 'bitbucket.org'. (+) * (Jurko) Add a test for handling actual non-ASCII unicode service location (+) data. (30.11.2013.) (+) * (Jurko) See if the suds HttpTransport.open() method ever gets called. (+) * Yup, transport open() methods get called from DocumentReader, e.g. (+) when downloading a WSDL schema from the net. (+) * It seems like Transport's open() & send() methods might be mergeable, (+) but that would first require further research. For now - YAGNI. (+) * (Jurko) Process pull requests received on 'bitbucket.org'. (+) * Fix setup.py current working folder path comparison so it works with (+) links in the path. Contributed by ryanpetrello. (23.12.2013.) (+) * (Jurko) Prepare a basic development script for running the full suds test (+) suite using multiple Python interpreter versions. (26.12.2013.) (+) * (Jurko) Process patches sent in by Bouke Haarsma on BitBucket. (+) * Unicode logging issue. (+) * Research. (+) * Prepare tests. (+) * Merge. (+) * Thorough code review. (+) * Implement a cleaner fix for both Reply & Request classes. (+) * Update release notes. (+) * (Jurko) Fix possible typo in the suds.transport.Request string/unicode (+) representation where there seems to be a missing space after a colon just (+) before the URL information. (+) * (Jurko) Remove unnecessary logger objects. (+) * (Jurko) Process the project issue #2 reported on BitBucket by Arthur (+) Clune, related to not being able to set the option cache location if the (+) default cache location is not a writable folder. (+) * Research. (+) * Prepare tests. (+) * Default cache. (+) * Default ObjectCache instance should be created only if no other (+) cache has been explicitly specified during suds.client.Client() (+) construction. (+) * Fix. (+) * Update release notes. (+) * Report back & close the project issue on BitBucket. (21.01.2014.) (+) * (Jurko) Process patch sent in by Bouke Haarsma on BitBucket to 'make sure (+) all web service operation parameters are consumed'. (+) * This patch has been worked on on a separate feature branch for close (+) to a month now. (+) * Update todo list. (+) * Update release notes. (+) * Commit. (+) * Push changes to BitBucket. (23.01.2014.) (+) * (Jurko) Prepare internal documentation notes folder. (+) * Add folder & add a descriptive readme.txt file to it. (+) * Include in the source distribution folder. (+) * Do not include it in the installation. (+) * Note the new documentation notes folder in the project's HACKING.rst (+) documentation. (24.01.2014.) (+) * (Jurko) Process Jurko's research & todo notes collected while working on (+) reporting extra parameter errors. (+) * (Jurko) Prepare a new suds-jurko 0.6 release. (+) * Check release notes. (+) * Test. (+) * Update version tag. (+) * Tag in Hg. (+) * Update version information. (+) * Retag in Hg (+) * Package the release. (+) * Distribute the new release. (+) * PyPI. (+) * BitBucket. (+) * Notify Gauthier Bastien - see comments for commit (+) 9da81de891958292850a242781b30a3493f617 on BitBucket. (+) * Prepare project information for the next development cycle. (25.01.2014.) (+) * (Jurko) Look into issue #12 & the related pull request #22 reported on (+) BitBucket - suds-jurko incorrectly returning a raw Fault() instead of a (+) WebFault() object when faults=False. Reported as a regression against the (+) base suds project. (+) * Research. (+) * Most likely introduced by commit (+) '506c8362c81343f1f629906606db23daf8b426ec'. (+) * Try to reproduce using an injected reply. (+) * Test against the current HEAD commit. (+) * Find a commit not that works correctly. (+) * Status. (+) * Could not reproduce the issue. Truly suds-jurko returns a (+) suds.sudsobject.Fault object, but the original suds library (+) implementation does not return a suds.WebFault object either. (+) Further work on this can be done only once the exact desired (+) behaviour is defined. (+) * Ask for feedback containing a reproducible example. (26.01.2014.) (+) * (Jurko) Code cleanup. (+) * Stylistic cleanup. (+) * (Jurko) Process decimal type support patch sent in by pendletongp on (+) BitBucket. (+) * Research. (+) * Where a Python type to SOAP representation transformation is (+) performed. (+) * Expected locations. (+) * When constructing a web service operation invocation (+) request, i.e. when marshaling Python data to a SOAP (+) request XML (mx package). (+) * Where a SOAP representation to Python type transformation is (+) performed. (+) * Expected locations. (+) * When processing a web service operation reply, i.e. when (+) unmarshaling Python data from a SOAP response XML (umx (+) package). (28.01.2014.) (+) * (Jurko) Process decimal type support patch sent in by pendletongp on (+) BitBucket. (+) * Prepare tests. (+) * Representation tests for different data types. (+) * Transformation tests for different data types. (+) * Thorough code review. (+) * Add XFloat tests. (29.01.2014.) (+) * (Jurko) Update XFloat to correctly translate decimal.Decimal & (+) numbers.Real (on Python 2.6+) values to their XSD value representation. (+) Currently, inconsistently with how other types are handled, they get (+) translated to the same decimal/fraction value which then gets changed to a (+) string later on when actually writing it to an XML string. This works out (+) fine for decimals in the end but not for rationals/fractions. (+) * Research. (+) * decimal.Decimal input values already get entered correctly into (+) constructed SOAP XML requests. (+) * It seems not all numbers.Real derived classes can be converted to (+) their XSD value representation the same way. (+) * Some examples. (+) * float --> str(x) (+) * fractions.Fraction --> str(float(x)) (+) * decimals.Decimal --> str(x) (+) * int --> str(x) (+) * bool --> str(int(x)) (+) * Adding support for them would pair down to simply listing all (+) supported types explicitly and adding code for translating (+) each of them separately. We can not possibly do this for 'all (+) data types in the world' so the current implementation seems (+) as good as any. (+) * A user application requiring support for a specific type can (+) always add it itself by implementing a replacement XFloat class (+) and registering it using suds.xsd.xbuiltin.Factory.maptag(). (+) * Discard the task. (+) * Add a new development note document describing XType class usage for (+) built-in XSD type value translation between Python objects and their (+) XSD value representations. (+) * (Jurko) Clean up different XType.translate() methods. See if there is any (+) difference in them returning a string or any other Python object (+) convertible to a string using str(). (+) * If there is no difference, clean up the code to simply return the (+) original value where possible. (+) * (Jurko) Process decimal type support patch sent in by pendletongp on (+) BitBucket. (+) * Research. (30.01.2014.) (+) * (Jurko) Process decimal type support patch sent in by pendletongp on (+) BitBucket. (+) * Research. (+) * XSD type value representation is incorrect. (+) * Apply equivalent changes together with adding related unit tests. (+) * Comment on the original issue #3 & patch request #19 on BitBucket. (+) * Update release notes. (+) * Later ideas to consider. (+) * 'float' --> 'decimal.Decimal' conversion which has not been (+) implemented in Python before Python 2.7. (+) * YAGNI for now. (+) * Update release notes. (31.01.2014.) (+) * (Jurko) Restore last_sent()/last_received() getters for last used SOAP (+) request/response messages. (+) * Research background. (+) * Requested by. (+) * andreebrazeau as BitBucket. (+) * 'http://bitbucket.org/andreebrazeau'. (+) * FerCesar at BitBucket. (+) * 'http://bitbucket.org/FerCesar'. (+) * Lucas Sampaio at BitBucket. (+) * 'http://bitbucket.org/lucassmagal'. (+) * Related links. (+) * 'http://stackoverflow.com/questions/4426204/ (+) how-can-i-output-what-suds-is-generating-receiving'. (+) * 'http://jortel.fedorapeople.org/suds/doc/ (+) suds.client.Client-class.html' (+) * Code removed in commit 'f0034d6826c179625478bc19ae2a39a0b803fc3a'. (03.02.2014.) (+) * (Jurko) Restore last_sent()/last_received() getters for last used SOAP (+) request/response messages. (+) * Clean up related code. (05.02.2014.) (+) * (Jurko) Clean up SoapClient/SimClient related code a bit. (+) * Generic code cleanup. (+) * Mark private methods using leading underscores. (+) * Remove RequestContext.original_envelope attribute. (+) * Stop logging incorrect 'original_envelope' value. (+) * Rename to _SoapClient/_SimClient. (06.02.2014.) (+) * (Jurko) Add tests for the functionality potentially affected by a planned (+) _SoapClient/_SimClient refactoring. (+) * Cache - update existing tests. (+) * File operation failures. (07.02.2014.) (+) * (Jurko) Update FileCache duration handling. (+) * Research. (+) * Currently FileCache.__set_duration() implementation allows using (+) only a single unit. (+) * Check whether Python 2.4 datetime.timedelta class implementation (+) allows it to be constructed from multiple units. (+) * Store FileCache.duration as a datetime.timedelta instance. (+) * Support for specifying FileCache durations with multiple time units. (+) * Add tests. (+) * Default suds.Client cache. (+) * Already tested in test_default_cache_construction(). (+) * Class. (+) * Duration. (+) * Default duration. (+) * Set duration (<0, 0, >0, single/multiple duration arguments). (+) * Exception on duration < 0. (+) * YAGNI. Current implementation, using negative values as (+) regular durations and simply adding them to a specific file (+) timestamp to see whether it expired seems good enough as well (+) as intuitive enough for the user. (+) * Expiration after expiration time with duration > 0. (+) * Not expiring after expiration time with duration = 0 (+) * Not expiring before expiration time with duration > 0. (+) * Not expiring before expiration time with duration = 0. (+) * Duration testing for all FileCache derived classes - DocumentCache (+) & ObjectCache. (+) * FileCache and derived classes - one item expiring should not (+) affect other unexpired items. (+) * Update release notes. (+) * (Jurko) Per process default FileCache folder randomization + removal on (+) exit. (+) * Research. (+) * Related BitBucket issue #15. (+) * Links from the BitBucket issue #15. (+) * 'https://bugzilla.redhat.com/show_bug.cgi?id=978696'. (+) * 'http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-2217'. (09.02.2014.) (+) * (Jurko) Per process default FileCache folder randomization + removal on (+) exit - fix CVE-2013-2217. (+) * Add tests. (+) * Implement. (+) * Update release notes. (+) * Update related issues on BitBucket. (+) * (Jurko) Extract all suds.client.Client related tests related to how it (+) uses its worker cache/store/transport components into separate (+) test_client.py test module. Modules like test_transport.py or (+) test_cache.py should concentrate on testing that specific component (+) functionality and not how the component gets used by suds.client.Client. (+) * Test modules to check. (+) * 'test_cache.py'. (+) * 'test_client_cache.py'. (+) * 'test_transport.py'. (+) * Several transport usage tests already prepared in test_transport_1.py (+) on Jurko's notebook. (10.02.2014.) (+) * (Jurko) Add tests for the functionality potentially affected by a planned (+) _SoapClient/_SimClient refactoring. (+) * Client's cache/store/transport usage. (+) * WSDL access. (+) * If an object is found in cache it should not be looked up in (+) the given document store or transported. (+) * If an object is found in a given document store it should not (+) be transported. (14.02.2014.) (+) * (Jurko) Clean up test_client.py test module - extract all remaining (+) suds.client.Client related tests related to how it uses its worker cache/ (+) store/transport components. (+) * Test modules to check. (+) * 'test_transport_http.py'. (+) * (Jurko) Update test_sending_non_ascii_data_to_unicode_URL() test to test (+) both send() & open() transport operations. (17.02.2014.) (+) * (Jurko) Clean up Transport ASCII/Unicode URL/data handling. (+) * Design desired behaviour. (+) * URL may be specified as either a byte string or a unicode string. (+) * URL input data may contain ASCII characters only. (+) * URL stored internally as a native str type (byte string with (+) Python versions prior to 3, unicode string with Python 3+). (+) * Using it in a different format breaks the underlying (+) HttpTransport httplib implementation under some Python (+) versions. (+) * Bytes data used directly. (+) * Unicode data used directly with the underlying implementation (+) converting the data to bytes or raising an error. (+) * HttpTransport. (+) * Python2 - converts to bytes if ASCII chars only. (+) * Python2 - error for non-ASCII chars. (+) * Python3 - error. (+) * Fix suds.transport Reply & Request string representation. (+) * Update suds.transport.Request. (+) * Check for non-ASCII URL characters. (+) * Hold it internally as a native str type. (+) * Update suds.transport.http.HttpTransport. (+) * Expects the suds.transport.Request to have already checked its URL (+) content. (+) * Document the desired design (docstrings). (+) * Request. (+) * Transport. (+) * Update tests. (+) * Request tests. (+) * Bytes URL. (+) * Must be used directly on Python 2. (+) * Must be converted to str URL internally on Python 3. (+) * Error if it includes non-ASCII character codes. (+) * Unicode URL. (+) * Must be converted to str URL internally on Python 2. (+) * Must be used directly on Python 3. (+) * Error if it includes non-ASCII character codes. (+) * HttpTransport tests. (+) * Sending data through the network. (+) * Remove tests made redundant by the new Request testing. (+) * Update release notes. (18.02.2014.) (+) * (Jurko) Add tests for the functionality potentially affected by a planned (+) _SoapClient/_SimClient refactoring. (+) * Client's cache/store/transport usage. (+) * If an object is found in cache it should not be looked up in the (+) given document store or transported. (+) * WSDL document. (+) * Imported XSD schema. (+) * Included XSD schema. (19.02.2014.) (+) * (Jurko) Add a failing test illustrating a WSDL import detected when (+) attempting to test importing a cached WSDL schema. (+) * test_WSDL_import(). (+) * (Jurko) Add tests for the functionality potentially affected by a planned (+) _SoapClient/_SimClient refactoring. (+) * Client's cache/store/transport usage. (+) * If an object is found in cache it should not be looked up in the (+) given document store or transported. (+) * Imported WSDL document. (+) * If an object is found in a given document store it should not be (+) transported. (+) * WSDL document. (+) * Imported WSDL document. (+) * Imported XSD schema. (+) * Included XSD schema. (+) * If an object is not cached or found in a given document store it (+) should not transported. (+) * WSDL document. (+) * Imported WSDL document. (+) * Imported XSD schema. (+) * Included XSD schema. (+) * Client's cache/store/transport usage. (+) * Different cachingpolicy option values. (+) * cachingpolicy == 0 - XML documents are cached. (+) * Already tested. (+) * cachingpolicy == 1 - final WSDL objects are cached. (+) * Loading a WSDL object from cache should avoid attempting (+) to fetch any additional external data either from the (+) cache, the document store or the registered transport. (20.02.2014.) (+) * (Jurko) Add tests for the functionality potentially affected by a planned (+) _SoapClient/_SimClient refactoring. (+) * Transport. (+) * Base Transport, Reply & Request classes already tested. (+) * HttpTransport. (+) * urllib2.HTTPError raised from urllib's open() operation. (+) * ACCEPT/NO_CONTENT status handling - buggy but YAGNI for (+) now. (+) * Add a separate todo item to look into this at some (+) later time. (+) * Other HTTPError exceptions. (+) * Other functionality already tested well enough. (+) * _SoapClient - currently can only be tested via suds.client.Client. (+) * Using Transport. (+) * 'nosend' option avoids transport usage. (+) * Sending data via & using data returned by transport. (+) * Opening a WSDL already tested. (+) * Sending a web service operation invocation request. (+) * Handling transport errors. (+) * This seems like a complex topic. Prepare a rough test and (+) add a todo item to deal with this in more detail later on. (+) * Embed a TODO comment next to the new test. (23.02.2014.) (+) * (Jurko) Refactor suds.transport.http module unit tests to make them (+) clearer. (27.02.2014.) (+) * (Jurko) Fix the raw try:/except: exception handling suds which may eat up (+) internal exceptions like SystemExit or KeyboardInterrupt but should not. (+) * Just replace with Exception subclass catching. A more detailed (+) exception class can be used later on if needed. (28.02.2014.) (+) * (Jurko) Fix Exception message used when attempting to construct a (+) suds.sax.element.Element with a non-Element parent. It seems like someone (+) forgot to apply % formatting there. (+) * (Jurko) Extract existing XML comparison test utilities into a separate (+) tests/test_utility.py module (CompareSAX class). (03.03.2014.) (+) * (Jurko) CompareSAX test utility improvements. (+) * Use pytest assertions to report errors. (+) * Add tests. (+) * Extract tests into a separate module. (+) * (Jurko) Fix the problem with different processes using different cache ids (+) for matching documents, causing them not to be able to reuse each other's (+) cache entries. Original suds implementation used the built-in hash (+) function which should be replaced with a md5 hash. (+) * On Python 3.3 and above hash function results are seeded with a random (+) value for each process. (+) * 32-bit & 64-bit Python implementations use different hash (+) implementations giving different results. (+) * Update readme. (+) * Problem reported by Eugene Yakubovich at bitbucket. (+) * See project related comments at: 'http://bitbucket.org/eyakubovich/ (+) suds/commits/fec6efd9c10b48114bbd9c06847a264453a51620'. (04.03.2014.) (+) * (Jurko) suds.sax.enc module cleanup. (+) * Code & comment cleanup. (+) * Add unit tests. (+) * (Jurko) Look into CDATA related encoding issues reported at BitBucket (+) (issue #14 + pull requests #25 & #26). (+) * Prepare tests. (+) * Prepare profiling scripts for comparing different solutions. (+) * Design input types. (+) * Long input. (+) * Replacements. (+) * None. (+) * Rare. (+) * Lots. (+) * CDATA. (+) * None. (+) * Short. (+) * Long. (+) * Short input. (+) * Has replacements. (+) * False. (+) * True. (+) * Has CDATA (+) * False. (+) * True. (05.03.2014.) (+) * (Jurko) Consider reimplementing suds.sax.enc.Encoder.decode() using (+) xml.sax.saxutils.unescape() as suggested by Stephen Fuhry (fuhrysteve at (+) BitBucket). (+) * Profile using the prepared tests.profiling.profile_sax_encoder module. (+) * Python 2.4.3. (+) * About the same for data containing many replacements. (+) * New solution much slower for data containing no replacements. (+) * Python 3.3.3. (+) * New solution a bit slower for data containing many (+) replacements. (+) * New solution much slower for data containing no replacements. (+) * Discard the new solution as it works the same or worse than the (+) current one. (20.03.2014.) (+) * (Jurko) Internal project development cleanup. (+) * Setting up Python installations used for testing suds. (+) * Research exact steps for installing required Python packages for (+) each specific Python version used. (+) * Prepare Windows batch script performing this operation en-masse. (23.03.2014.) (+) * (Jurko) Report 'except:' issues in setuptools project's ez_setup.py (+) script. (+) * (Jurko) Update the project setup procedure to use any preexisting (+) setuptools installation of version >= 1.4 but install the latest (+) compatible setuptools version if a suitable one is not already installed. (+) * YAGNI - using an older and potentially untested setuptools version (+) risks our project installation failing without the user 'doing (+) anything wrong', and gains us only a bit lighter installation in some (+) scenarios. (+) * (Jurko) Update the project setup procedure to use setuptools 1.4.2 for (+) Python versions prior to 2.6, and the latest setuptools version 3.3 for (+) all newer Python versions. (+) * Update the project's setup.py script. (+) * Use setuptools 1.4.2 with Python < 2.6. (+) * Use setuptools 3.3 with Python >= 2.6. (+) * Update project hacking documentation. (+) * Check for newer setuptools versions with every new suds release (+) and update the project's setup.py script as needed. (+) * Update release notes. (15.05.2014.) (+) * (Jurko) Process work done 'as time allowed' during the last two months. (+) * Commit prepared basic environment setup scripts. (+) * Mark related todo items now completed or rendered obsolete. (+) * (Jurko) See if we can make the project setup.py script work when run from (+) a folder other than the project's root folder. (+) * Managed to improve the support a bit, but distutils still has issues (+) regarding this. Should be checked again after a while to see if the (+) situation has changed. (+) * (Jurko) Internal project development cleanup. (+) * Setting up Python installations used for testing suds. (+) * Prepare a script for installing required Python packages. (+) * Convert prepared Windows batch script to Python. (+) * Required Python packages. (+) * setuptools. (+) * pip. (+) * pytest. (+) * Update project hacking documentation. (+) * Allow setting up only specific Python versions. (+) * Can be configured using setup.cfg for now. (+) * Update to only install packages that have not already been (+) installed. (+) * Transformed to embedded TODO comments. (+) * Make sure the prepared Python script runs using any of the (+) suds project supported Python versions. (+) * Collect all downloaded data under a single folder. (+) * Targeted content. (+) * ez_setup.py downloaded data. (+) * easy_install downloaded data. (+) * pip downloaded data. (+) * See if using the pip download cache folder is useful at all (+) since we already pre-download all the required installation (+) packages and use them from there. (+) * Prepare a test script installing suds into multiple Python versions. (+) * Without setuptools preinstalled. (+) * With setuptools preinstalled. (+) * setuptools 1.4. (+) * Latest compatible setuptools release. (+) * setuptools 1.4.2 for Python releases prior to 2.6. (+) * (Jurko) Plan preparing a wheel based distribution. (+) * Test it. (+) * Document. (+) * Setting up the necessary development environment. (+) * Release notes. (+) * (Jurko) Find a cleaner way to install suds tests. Currently they get (+) installed into a top-level 'tests' folder and so may cause conflicts with (+) some other 'tests' package that might exist in the target Python (+) environment. (+) * Ideas to consider. (+) * We want all suds users to have easy access to the suds test suite. (+) * Suds users are programmers themselves, and so we want to make (+) it easy for them to reproduce their issues as easily, (+) concisely and directly as possible, and using existing suds (+) tests seems like a perfect starting point for doing just that. (+) * Placing the 'tests' folder under the 'suds' folder. (+) * Placing the top level suds folder outside the egg folder (similar (+) to how this is done for the pytest package), not having the egg (+) folder added to the Python path and leaving the tests folder (+) inside the egg folder. (+) * Have setup.py install suds tests as a setuptools 'extra' - only if (+) explicitly requested. (+) * At first glance, this does not seem like something we would (+) like done, as we want all suds users to have easy access to (+) the suds test suite and not just those installing suds from (+) its source distribution and providing some magic (+) '--install-tests'-like command-line option. (+) * Keep the tests in the source distribution and do not install them. (+) This seems like the simplest way, so lets go with this for now. (16.05.2014.) (+) * (Jurko) Find a cleaner way to install suds tests. Currently they get (+) installed into a top-level 'tests' folder and so may cause conflicts with (+) some other 'tests' package that might exist in the target Python (+) environment. (+) * Update the tests so we no longer need to transform them using py2to3 (+) in order to run them using Python 3. (+) * Try using the six compatibility package. (+) * Manual install. (+) * Update setup.py. (+) * Update tools/setup_base_environments.py. (+) * Update tests. (+) * Make the tests run using the 'currently installed' suds version. In (+) order to test the current development version, user should install the (+) project in development/editable mode using 'pip install -e' or (+) 'setup.py develop'. (+) * Running 'setup.py test' should still work using both Python 2 & 3. (+) * Make project tests run from the root project folder use the correct (+) installed Python 3 sources if run under Python 3 instead of using the (+) ones from the current folder. (+) * No easy way to do this for now. Should look into it again at some (+) later time. (+) * Update project release docs. (+) * Update project HACKING.rst docs. (+) * Using the 'six' Python 2/3 compatibility package. (+) * Running project tests using Python 3 - current working folder. (22.05.2014.) (+) * (Jurko) Test using Python 3.4.1. (26.05.2014.) (+) * (Jurko) Remove code duplication between setup.py & (+) tools/setup_base_environments.py, e.g. with regards to setting up the (+) project's testing requirement packages (argparse, colorama, py, pytest). (29.05.2014.) (+) * (Jurko) Internal project development cleanup. (+) * Re-implement the existing run_all_tests.cmd Windows batch script for (+) running a full suds test suite on multiple Python versions in Python. (+) * Read the list of target Python environments from the project's (+) setup.cfg configuration file. (+) * Make sure the prepared Python script runs using any of the suds (+) project supported Python versions. (+) * Add support for disabling testing a specific Python platform. (+) * YAGNI for now. Can be done by commenting out appropriate (+) environment definition sections in the main project 'setup.py' (+) configuration file and will be affected later on by porting (+) our testing system over to use the tox project. (+) * Update HACKING.rst documentation. (14.06.2014.) (+) * (Jurko) Add basic suds.sax.element unit tests. (15.06.2014.) (+) * (Jurko) Add tests for suds.sax.element.Element string conversion. (+) * (Jurko) Add tests for suds.sax.document.Document string conversion. (+) * (Jurko) Fix suds.sax.document.Document str conversion bug. (+) * (Jurko) Fix original suds project bug with suds.cache.DocumentCache not (+) caching suds.sax.document.Document instances correctly, even though such (+) instances get passed to it by the suds.reader.DocumentReader. (+) * (Jurko) Fix problems with suds silently hiding plugin exceptions. (+) * Add plugin unit testing module (+) * Test that calling a plugin using PluginContainer passes any (+) exceptions raised by the plugin. (+) * Fix the PluginContained exception passing issue. (+) * Close issue #42 at BitBucket. (+) * Close pull request #33 at BitBucket. (+) * (Jurko) Fix problem with decimal zero representations having a negative (+) exponent, e.g. as it "0.0000". (+) * Add tests. (+) * Fix failing assert. (+) * Close pull request #37. (+) * Close issue #31. (23.06.2014.) (+) * (Jurko) Look at issue #49 at BitBucket - generated SOAP request containing (+) a tag with a missing namespaces identifier. (+) * Research. (+) * Caused by incorrect XSD schema element form attribute handling (+) when the element is actually just a reference to another top-level (+) element. In such cases the the referenced and not the referencing (+) element's form attribute value should be used. (+) * Prepare a quick-fix plugin. (+) * Report back to the BitBucket issue tracker. (24.06.2014.) (+) * (Jurko) SchemaObject.form_qualified cleanup. (+) * Add tests for the schema element's form attribute handling. (+) * Element with form attribute set to 'qualified'. (+) * Schema with elementFormDefault set to 'qualified'. (+) * Schema with elementFormDefault set to other than 'qualified'. (+) * Schema with elementFormDefault unset. (+) * Element with form attribute set to other than 'qualified'. (+) * Schema with elementFormDefault set to 'qualified'. (+) * Schema with elementFormDefault set to other than 'qualified'. (+) * Schema with elementFormDefault unset. (+) * Element with unset form attribute. (+) * Schema with elementFormDefault set to 'qualified'. (+) * Schema with elementFormDefault set to other than 'qualified'. (+) * Schema with elementFormDefault unset. (26.06.2014.) (+) * (Jurko) Remove unnecessary 'history' list copying in (+) suds.xsd.sxbase.SchemaObject.content(). (+) * (Jurko) suds.xsd.deplist module cleanup. (+) * Stylistic code & comment cleanup. (+) * PEP8-ify. (+) * Make pop() not ignore exceptions. (+) * Make add() no longer return self. (+) * Remove sorted member. (+) * Order methods alphabetically. (+) * Mark private operations & data as private. (+) * Review the topological sort implementation. (+) * `popped` list seems to be filled only for its elements to later be (+) moved over to the `sorted` list. (+) * Add topological sorting tests by extracting the existing embedded test (+) into a stand-alone unit test module. (+) * (Jurko) Look at BitBucket issue #50 - SOAP 1.2 compatibility. (+) * (Jurko) Look at BitBucket issue #51 - complexType 'mixed' attribute. (27.06.2014.) (+) * (Jurko) suds.xsd.deplist module cleanup. (+) * Reference cycle handling. (+) * Research. (+) * Document. (+) * Test. (+) * Review the topological sort implementation. (+) * See if there is anything obvious to be made more efficient in it. (+) * Simplify code by using a recursive implementation. (+) * Refactor the DepList class into a single function dependency_sort() (+) function taking a single dependency dictionary as input. (+) * Refactor code. (+) * Rename module to suds.xsd.depsort. (+) * Update release notes. (+) * DepList class replaced with a simple dependency_sort() function (+) taking a single dependency dictionary as input. (+) * The original implementation's interface was too heavy-weight (+) with no added value. (+) * Anything tried with the original interface outside the basic (+) use-case covered by dependency_sort() was actually or could be (+) easily broken. (+) * suds.xsd.deplist module renamed to suds.xsd.depsort. (+) * (Jurko) SchemaObject.form_qualified cleanup. (+) * Add tests for the schema element's form attribute handling. (+) * Reference elements. (+) * Unqualified referencing qualified. (+) * Referencing & referenced elements should have different (+) namespaces. (+) * Qualified referencing unqualified. (28.06.2014.) (+) * (Jurko) SchemaObject.form_qualified cleanup. (+) * Fix detected issues. (+) * Fix failing tests. (+) * Mark tests as no longer expected to fail. (+) * Update project release notes. (+) * Report back to issue #49 on BitBucket. (29.06.2014.) (+) * (Jurko) SchemaObject.form_qualified cleanup followup. (+) * Research the XSD specification on the element form attribute. (+) * Correct suds's XSD element form attribute handling. (+) * Reference elements are always qualified. (+) * Top-level elements are always qualified. (+) * Non-top-level non-reference elements are qualified based on their (+) form attribute, and if they have no form attribute, their schema's (+) elementFormDefault attribute is used instead, and if that one is (+) not given either - they are considered unqualified. (28.06.2015.) (+) * (Jurko) Clean up WSDL imports. (+) * Remove invalid test_WSDL_import() test & related comment in (+) `test_client.py`. (+) * Add WSDL import tests. (+) * Import WSDL with same target namespace. (+) * Import WSDL with different target namespace. (+) * (Jurko) Fix recursive WSDL import issue. (+) * Add tests. (29.06.2015.) (+) * (Jurko) Fix recursive WSDL import issue. (+) * Fix issue. (30.06.2015.) (+) * (Jurko) Review and commit unpublished cleanup work on Jurko's machine. (01.07.2015.) (+) * (Jurko) Fix recursive XSD import issue. (+) * Add tests. (+) * Fix issue. (14.07.2015.) (+) * (Jurko) Fix issue https://bitbucket.org/jurko/suds/issues/90 - code in (+) `mx/typer.py` Typer.genprefix() broken by commit (+) 103efca86cd07e9d7205ffb2735748f275a4b35f. (+) * Add todo note to improve test coverage that managed to miss this (+) problem. (+) * Fix. (+) * (Jurko) Add basic unit tests for Typer.genprefix(). (+) * Got broken in commit 103efca86cd07e9d7205ffb2735748f275a4b35f and (+) fixed as part of issue https://bitbucket.org/jurko/suds/issues/90 but (+) the bug was missed by the existing test suite. (25.07.2015.) (+) * (Jurko) Clean up some issues from BitBucket. (26.07.2015.) (+) * (Jurko) Clean up some issues from BitBucket. (27.07.2015.) (+) * (Jurko) Clean up some issues from BitBucket. (28.07.2015.) * (Jurko) Review and commit unpublished cleanup work on Jurko's machine. * (Jurko) Clean up WSDL imports. * Add tests for the compatibility kludge with existing WSDL schemas in the wild that use WSDL imports to import XSD schemas. * `Definitions` object is also used to load XSD schema content referenced using a WSDL import element. This should be reviewed as it seems like a quick-hack. According to the `Definitions` class comment, its instances are intended to model data loaded from WSDL documents only. If we want to load XSD documents and WSDL documents the same, we should load them as generic XML documents (or at least check their root XML element) and proceed from there with a different loading system. * An importing WSDL should not be allowed to reference XSD entities from an imported WSDL. If such data needs to be referenced, it should be imported from an external XSD schema (either using XSD import or using the questionably standard and not widely supported WSDL import variation importing an XSD instead of a WSDL schema). * We could possibly retain this XSD importing feature as an optional kludge for compatibility with broken web services. * Makes no sense to collect all types, messages, port_types & bindings data from imported WSDLs. * We currently collect types, messages, port_types & bindings entities but do not collect services or integrate schema elements. * All this seems quite messy. * See why this data is actually collected. * Random thoughts. * Possibly collect only the actually used ones, i.e. those referenced from the importing WSDL's entities. * If the collected data is just some internal implementation detail, mark its member variables as private (name with a `__` prefix) and comment on what it is they actually contain. * (Jurko) SchemaObject.form_qualified cleanup followup. * Correct suds's XSD element form attribute handling. * Qualified XSD elements are connected to a namespace. * Unqualified XSD elements are not connected to any namespace. * XML elements matching an unqualified XSD schema element do not necessarily need to have no namespace specified. If their parent element's default namespace is specified, then they still need to explicitly specify their default namespace as an empty string. * Test SOAP XML construction for qualified/unqualified elements with and without the `prefix` suds option enabled. * Document breaking changes between this fork and the original suds project. * DocumentStore interface changes. * Choice support. * SchemaObject.form_qualified should not have 'form_qualified'. That seems like something related only to elements & attributes. * SchemaObject.dependency() method seems too complicated - its interface allows returning a list of dependencies, with a specific one amongst them singled out as a dependency to be merged, when in fact it always returns no dependencies or only a single dependency with that single dependency to be merged. * Rename to dereference() and have it return the referenced SchemaObject - since that is in fact what this function already does. * SchemaObject.merge() is misnamed - it should be named collect_referenced_data() since it is a completely different operation from Schema.merge() and it actually only collects data from a referenced object, if such an object exists. * Look at issue #46 at BitBucket - form attribute on schema attributes does not seem to be handled correctly. * Schema 'attributeFormDefault' attribute handling. * Schema attribute 'form' attribute handling. * (Jurko) Look at the Spyne project and its suds usage. * Try running Spyne's test suite using the suds-jurko fork. * Related links. * 'https://spyne.ci.cloudbees.com/job/spyne/PYFLAV=2.7/ lastCompletedBuild/testReport/spyne.test.interop.test_suds/ TestSuds/'. * 'https://github.com/arskom/spyne/blob/master/spyne/test/ interop/test_suds.py'. * Notes from the Spyne maintainer Burak Arslan. * You can run Spyne tests against your suds locally. Just clone the Spyne repo and edit setup.py to use suds-jurko instead of suds here: 'https://github.com/arskom/spyne/blob/master/setup.py#L344'. * Send feedback back to the Spyne maintainer (burak.arslan at arskom.com.tr). * Try updating Spyne project test system so it can be easily configured to use suds-jurko instead of the official suds release. * (Jurko) Try out virtualenv to make it easier to run tests using multiple Python versions and to allow us to automate suds installation testing in the future. * Compatible virtualenv versions. * virtualenv - 1.7.2 (last before 1.8) - last on Python 2.4. * virtualenv - 1.9.1 (last before 1.10) - last on Python 2.5. * (Jurko) Internal project development cleanup. * Prepare a full suds test suite subset that can be used for regular development - select only a few platforms to test suds on regularly and run the full test suite only 'when needed', e.g. before pushing changes onto BitBucket or at the end of a single development session. * (Jurko) Add an XML comparison test utility supporting raw XML data comparison with the following features. * Important comparison features. * How textual data is split between child elements. * Child element ordering. * Element/attribute namespace + name. * Non-namespace declaration related attribute ordering. * Unimportant comparison features. * Leading/trailing textual data whitespace per line for mixed content nodes containing at least one child element. * Element/attribute namespace prefix. * Namespace declaration related attribute ordering. * Namespace declarations (no prefix). * Namespace prefix declarations. * (Jurko) Add tests for the functionality potentially affected by a planned _SoapClient/_SimClient refactoring. * _SoapClient - currently can only be tested via suds.client.Client. * 'prettyxml' option. * 'retxml' option. * _SimClient - currently can only be tested via suds.client.Client. * Using Transport for sending an injected request. * Not using Transport when a reply has been injected. * _SimClient using injected request. * _SimClient using injected reply. * Success. * Failure. * Status & status description. * 'nosend' option. * With injected request data. * With injected response data - ignored. * 'retxml' option. * 'prettyxml' option. * RequestContext. * Returned from _SoapClient. * Returned from _SimClient. * Processing an externally provided reply. * Plugin processing. * Variants. * _SoapClient/_SimClient. * _SimClient with injected reply/response. * 'nosend' option + RequestContext reply. * 'retxml'. * 'prettyxml'. * Fetched documents. * document.loaded(url, bytes) --> bytes. * document.parsed(url, document). * Documents read from cache. * document.parsed(url, document). * Client initialized with WSDL. * init.initialized(wsdl). * SOAP requests. * marshalled(document) - may modify document in-place. * sending(bytes) --> bytes. * SOAP replies. * received(bytes) --> bytes. * parsed(element) --> may modify document in-place. * unmarshalled(result) --> result. * (Jurko) Refactor _SoapClient/_SimClient related code. * Refactor. * Already prepared as client_1.py on Jurko's notebook but need to add relevant tests first. * Test. * _SoapClient subclasses modifying _init_invocation_context(), _get_request() & _get_reply(). * Update release notes. * Client debug log messages changed. * Removed the RequestContext.original_envelope attribute. * (Jurko) See why creating two suds.client.Client instances with the same transport instance fails. It reports some 'Duplicate domain "suds.options" found' error. t = suds.transport.Transport() tests.client_from_wsdl(tests.wsdl(""), transport=t) tests.client_from_wsdl(tests.wsdl(""), transport=t) * (Jurko) Generic code cleanup. * HttpTransport tests. * Check for multiple build_opener() calls. * Check for multiple urlopener.open() calls. * Check what gets passed to the default built urlopener. Merge this with externally specified urlopener testing. * Add HttpTransport proxy configuration tests. * See when the proxy member is set. * See what the default proxy member value is for. * See whether the proxy member is used at all with an externally specified urlopener, and if so - if it is made redundant by using such externally specified urlopeners. * Add cachingpolicy option tests. * Add default document store tests. * Generic. * suds.client.Client document store usage. * See why exception raised when passing a transport to suds.client.Client that is not a subclass of suds.transport.Transport is formatted as it is. * Class's string representation wrapped in a tuple. * Uses class's repr() string representation. * Double quotes used around the 'transport'. * suds.store.defaultDocumentStore cleanup. * See if this member should be marked as private. * Rename to not use camelCase. * DefinitionsReader.open() changes options on cached Definitions instances. Make sure this does not break programs with multiple suds Clients loading the same WSDL schema but using different options. * Support for calling NoCache.purge() & NoCache.clear(). * Consider making FileCache.clear() remove files matching both prefix & suffix instead of only the prefix. * Might be ok for explicitly called clears, but not for those called due to the cache version information getting changed. This might need to be worked around or commented/tested in code. * Make FileCache, DocumentCache & ObjectCache all consistently remove or not remove cache entries on failed access. Possibly do so for the base Cache class as well. * Make Cache.put() not return the cached data. * FileCache.put() operations should not leave behind invalid files in case of failed write operations. Possibly first store a valid file under a temporary name and then rename it. * (Jurko) Restore last_sent()/last_received() getters for last used SOAP request/response messages. * Research. * Difference between how _SoapClient & _SimClient stored their last sent/received messages. * Both stored them. * _SimClient stored the simulated fault as its last received message. * Whether sent/received messages should be returned/logged before or after being processed by registered plugins. * _SoapClient stored the received reply after it got processed by registered plugins. * Sending with _SoapClient. * Sending with _SimClient. * Which plugins get called and when. * Add back the removed code. * Add tests. * Notify people interested in this. * (Jurko) Clean up input argument/parameter & wrapped/unwrapped terminology usage in code. * Synchronize all usage both in code and in relevant 'notes/' documents. * (Jurko) Triage received issues & pull requests at BitBucket a bit and prioritize them in relation to already existing todo items on this list. * (Jurko) Process Jurko's remaining research & todo notes collected while working on reporting extra parameter errors. Some are redundant or have already been dealt with so the list needs to be triaged first. * Fix non-optional choice element handling - missing values should be used as empty strings, same as for non-choice parameters - search for tests xfailed with reason 'non-optional choice handling buggy'. * Add tests: which type XSD schemas do not get unwrapped automatically, e.g. simple types such as built-in type restrictions. * Split up test_request_construction.py tests into parameter definition, argument parsing & possibly partial binding specific request construction tests. * Add test: single parameter pointing to an element - parameter name should be ignored. * Add test: single parameter pointing to an element - with unwrapping. * Add test: single parameter pointing to a type - with unwrapping. * Add test: single parameter pointing to a built-in type - no unwrapping. * Add test: multiple parameters pointing to types & elements. * Add tests: WSDL tests - XSD schema content. * Add tests: WSDL tests - recognized operations. * Add tests: WSDL tests - recognized services. * Add test: document/literal, document/encoded, rpc/literal & rpc/encoded should make no difference to input processing in theory and should all recognize the same input parameter structure. * Add tests: WSDL tests - recognized binding - input/output - for different methods - document binding should be the default - see whether literal should be the default binding style. * Add test: named sequence/choice/all - there is no such thing - their name attributes should be ignored. * Research: consider input parameter elements with missing type declarations to have the type "xsd:string" or "xsd:any" or something similar - check the XSD specification. * '' currently used wrapped. * ' ' currently used unwrapped. * Add test: unwrapped element reference input parameter. * Add test: shema_object.resolve() & shema_object.resolve(nobuiltin=True). * Add test: explicitly marking an input parameter sequence as optional should be recognized when unwrapping those input parameters - such functions should report accepting 0 parameters. * Add test: expected input argument count reporting with choice input parameters explicitly marked optional. * Add test: array parameters. * Research: whether web service operations taking multiple input parameter body-parts can have those body-parts simply reference other elements, thus requiring us to resolve that reference before checking whether the input parameter is optional (and possibly other places as well - see Document.mkparam() which does no resolving for such web service operations but does so for operations taking a single wrapper input parameter structure). * Research: see if the parameter type checked whether it is optional inside Document.bodycontent() needs to have its ancestor elements checked as well - this might be needed if this can actually be a reference to some other, more deeply nested element. * Research: can element references be chained - nope as they are allowed to reference only top level elements and top level elements are explicitly not allowed to use the ref attribute. * Research: can element references be optional by themselves - yes, and that is the only way they can be marked as optional since they may only reference top-level elements and those are in turn not allowed to use the minOccurs/maxOccurs attributes. * Add test: extra array parameters - more than maxOccurs - possibly not necessary if we want to allow suds to make such 'illegal' calls. * Add test: missing regular parameters. * Add test: missing non-optional choice parameters. * Add test: missing optional choice parameters. * Recognize missing optional values when specified as a lists/tuples containing only values None and other such lists/tuples. * Error when passed a list/tuple argument containing a list/tuple or None. * Research: web service operations taking multiple same-named parameters - how they are and how they should be handled. * Choice containing an empty sequence should be recognized as optional. * Choice(a, sequence(b, c)) should set b to '' if it is not optional and no value is given for it, but a value is given for c - same if b & c roles are reversed. * Research: suds does not seem to know how to handle minOccurs/maxOccurs attributes specified on sequence & choice XSD schema XML elements - it definitely does not recognize elements inside an optional choice or sequence as optional - see how this affects input/output parameter unwrapping. * Add test: passing an array element value as an empty list. * Add test: passing an array element value as a non-empty list. * Add test: constructing requests with missing optional parameters - should not be specified in the request. * Add test: constructing requests with missing non-optional parameters when the operation also takes some optional parameters - how this is reported. * Add test: constructing requests by using positional arguments when the operation takes first optional input parameters and then non-optional ones after that - initial positional arguments should be mapped to the initial optional input parameters. * Research: constructing requests with missing default parameters - see whether this should be reported as an error or added to the request as an empty value. * Research: self.mkparam() call in Document.bodycontent() may return a list in which case later setPrefix() method call on the returned object will raise an AttributeError exception. * Detecting and reporting multiple input parameter values specified for a single input parameter choice group (empty choice group, simple single choice, multiple independent choices, one choice group inside another, one choice inside another but with additional elements between them). * Research: whether specifying an empty list or tuple for a parameter should be treated the same as None - if so, add tests - possibly []/ (,) really skips the parameter while None gets mapped to an empty value for non-optional parameters.. * Support for multiple same-named elements in a single input or output structure - those directly back-to-back with each other can already be set or accessed as arrays but we could need to support others as well. * Constructing a request for a web service operation taking a single empty all/choice/sequence parameter - should be the same as for operations taking no parameters. * Constructing requests for web service operations taking multiple choice parameter groups (as positional and keyword arguments). * Using a function with multiple choice parameter group values. * TestExtraParameters.test_function_with_no_parameters() - why does using WSDL '' not work - seems not to be recognized as wrapped data but if the XML element has any data (e.g. a space or a new-line) then it gets recognized correctly - smells like this could be a bug. * Web service operation taking multiple input parameter bodyparts. * None of which is an empty sequence. * At least one of which is an empty sequence. * Unrelated issues noticed while working on this. * Document.document() - returned temporary can be inlined. * Core.process() - root local variable seems redundant. * Document.bodycontent() - mkparam() does not seem to be able to return None but if it does, it might mess up tracking which argument got used and we should document when it does that. * Parsing parameter values (interpreting positional & keyword arguments, mapping them to internal web service operation parameters, reporting duplicate parameter values, reporting unrecognized parameters and skipping ignored or None choice item parameters) should not be binding class specific, i.e. it should be shared for both document & rpc bindings. * Code cleanup * It seems suds ignores top-level element names & namespaces when parsing server responses and instead just uses them by their index. This seems wrong. Check the WSDL specification, SOAP message binding and other related standards to see if the server must always return its values in the correct order in which the corresponding output message parts are defined in the WSDL. Plan updating suds to report invalid server responses. * Replace except: blocks with blocks catching specific exception types. * suds.xsd.schema module imports suds.xsd.sxbuiltin.Factory as BuiltinFactory but never used that reference. See if this interface is perhaps documented as published and if not - remove it. * Performance tuning - SchemaObject.str() should not use len(self) to check whether it is empty. * SchemaObject.str() - see whether any contained item information is included if none of the items are instances of Content. * TypedContent.qref() - see whether len(self) == 0 check for 'simple types' can be improved. There should be no need to calculate the whole length just to determine that the object is empty. * Simple.mixed(), SimpleContent.mixed() - see if they really need to return the current object length or if they can just check whether the object is empty. * suds.sudsobject.footprint() - see if the len() check is really needed just to see if the object is empty. * Check other len() usage in suds.sudsobject. * Check all 'except:' blocks to see if they actually need to be such catch-all blocks. * (Jurko) Look into issues #7 & #13 on BitBucket - problems loading a WSDL containing a recursively defined XML schema. * Possibly already fixed by related work done around 01.07.2015. NON PRIORITIZED: ================================================= * Find and use a good spy/mock framework for our tests instead of rolling our our own every time we need one. * Add a functional test parsing a WSDL and running all the way down to the Typer.genprefix() function. * Useful to help see what else got missed in the test suite when the suite missed this function and let through a bug reported as https://bitbucket.org/jurko/suds/issues/90. * Improve Typer.genprefix() implementation. * See whether, when we have the same namespace already mapped, we should return its existing prefix here. * Current implementation already does that but only in some cases - if ns1, ns2, ... ns# are # all already mapped to a different namespace and ns#+1 is mapped to the one we want, it returns ns#+1, making it seem like this was tacked on only as an afterthought. * suds.sudsobject.footprint() does some fancy recursive counting while all it is actually used for is for detecting objects with footprint 0. This could be optimized by replacing it with a function returning simply whether a given object has a non-empty footprint and returning as soon as it find something of value in it. * Add suds.sax.element related tests. * append(). * Add child element. * Add child attribute. * childAtPath(). * With namespaces. * With prefixes. * Empty child name - as initial child - requested. * Empty child name - as initial child - pass through. * Empty child name - as indirect child - requested. * Empty child name - as indirect child - pass through. * Handling multiple children with matching names - is there a way to specify an index. * Add child by constructing it with a parent argument - should automatically add the element to the parent's children list, while the current implementation only makes the child know about the parent and does not let the parent know about the child. * Constructing an element with '/' in its name should not be allowed since that character is used as a part separator. * See if a way to escape such character usage is needed and if so - how to do it. * Handling multiple children with matching names. * childrenAtPath(). * Initializing a new element with a specific namespace. * Add suds.sax.document unit tests similar to those already added for suds.sax.element. * Some ideas on what to not forget. * See suds.sax.element related tests & todo notes. * childAtPath(). * childrenAtPath(). * No root element. * Look into CDATA related encoding issues reported at BitBucket (issue #14 + pull requests #25 & #26). * Current status: waiting for further feedback or progress on BitBucket. * Implement a working solution satisfying the prepared tests. * Use the prepared profiling script to make sure this change does not degrade general suds performance. * Get more feedback on a seemingly bad 'embedded CDATA section' test included in the pull request #26 on BitBucket. * Support for nested CDATA sections, in violation of the CDATA related documentation found at 'http://en.wikipedia.org/wiki/CDATA#Nesting' and 'http://www.w3.org/TR/REC-xml/#sec-cdata-sect'. * Decide how unclosed CDATA sections should be handled. * Suggestion: raise an exception since this is not allowed in well formed XML documents. * Resolving XSD schema elements currently returns an XSD schema type object representing the element's type. Different elements with matching types should possibly resolve to the same XSD schema type object but currently do not. See if this behaviour should be corrected. * Example. * XSD schema: * Test code: schema = client.wsdl.schema element_in = schema.elements["wi", "my-namespace"] element_out = schema.elements["wo", "my-namespace"] assert element_in.resolve() is element_out.resolve() * Both elements should possibly resolve to the same XString instance, but currently they resolve to two separate ones. * Optionally make suds check that the input parameters passed to its web service proxy operations actually match their respective WSDL definitions. * Current suds behaviour. * Extra parameters are ignored. * Parameters of invalid type are simply added into the generated SOAP request as strings and left up to the web service implementation to deal with and report as an error if the constructed SOAP request turns out to be invalid. * Having a local client-side check could make catching client side programming bugs easier. * Ideas. * Make sure given values fit their respective type value domains, e.g. integers, strings, regular expressions, restrictions, complex types where a builtin was expected, complex type of an unexpected structure, etc. * Extra parameters are reported as errors. * Missing non-optional parameters are reported as errors. * Input message part has both element & type specified. * See how invalid schemas containing a ref-cycle are handled. * They should be reported as invalid either when dereferencing them (e.g. to determine an element's target namespace) or when building the internal schema object tree and should not cause us to go into endless recursion. * See how multi-occurrence input parameter elements are supposed to be supported. * With automated parameter unwrapping support. * Without automated parameter unwrapping support. * Clean up & correct the choice support implementation. * Choice parameters seem to be supported only for document/literal style input parameters only. * Add tests for this. * Fix. * See the currently disabled 'xxxtest_choice_parameter_implementation_inconsistencies' unit test demonstrating a problem with the current implementation. * _SoapClient 'location' cleanup. * URL quoting, especially if specified externally by the caller instead of having been read from a valid WSDL schema. * See how input parameter element for a document/literal web service operation style gets handled when that element has 2 occurrences. * See how this affects suds library's automated input data unwrapping. * See how this sort of data is supposed to be entered anyway. * FileCache class fixes. * Remove incorrectly created cache files, e.g. if one gets created but then writing to it fails. * Make sure reading a cache file does not crash the whole Python interpreter process. * Encountered when trying to read an empty cache file using a debug Python version 3.2.3 on Windows 7 - triggered an internal Python assertion failure. * Look into the 'suds.null' class. * Research. * See sources (grep for 'null'), old release notes (README.txt) and commit messages. * What it is for. * Whether it can be replaced with None. * If it serves some purpose see if it should be used for identifying missing 'choice' structure members as well. * Make it simpler to run Py3 tests. * Current status. * May be run without additional pytest options by running 'setup.py test' from the root project folder. * This method will automatically download & install pytest from PyPI if needed, but will leave their installations behind in the project's root folder. * May be run by first building the project by installing the project, e.g. by running 'setup.py develop' and then running 'pytest' from the project's 'tests' folder. * Do some more thinking on this and, when done, update related HACKING notes. * Generate suds Python library documentation (epydoc). * Research. * HTML. * PDF. * Decide how to maintain, generate & distribute this documentation. * Update project HACKING notes to note the external software required for generating the documentation. * Update release procedure to include releasing the documentation. * Once the official project documentation has been integrated into this fork, go through existing internal project notes in our fork and see how to integrate them into the official project documentation. * Research. * Test how optional elements under a choice work. * There are some comments & an additional patch related to this at 'https://fedorahosted.org/suds/ticket/342'. * Default element values. * What they actually mean. * From from 'http://www.w3.org/TR/xmlschema-0'. * If the element does not appear it is not provided; if it does appear and it is empty, its value is the specified default value; otherwise its value is that given. * How elements with default values inside a choice structure should be handled. * See what the suds.sudsobjects.Facade class is for. * How to implement test cases requiring a test web service. * See how to connect to a web service via a Proxy server requiring NTLM authentication. * There are some projects seen on the net implementing a NTLM authentication handler for urllib. * Testing this will require implementing a proxy server requiring NTLM authentication and a web service or at least a web service requiring NTLM authentication. * Using pylint. * Implement an urllib connection handler allowing connecting using HTTPS with client authentication. * Prepare a test (will require a test web service). * Implement. * Prepare additional test cases. * Loading a WSDL from a file as in suds.client.Client( "file://localhost/Folder/aaa.wsdl"). * Path not containing spaces. * Path containing spaces. * Invalid element reference. * Handling schema imports. * With the same target namespace. * With a different target namespace. * Prepared SOAP operation invocation requests. * With choice parameters. * Calling a web service operation with no parameters. * RPC binding style. * Document binding style. * Process ideas collected from external projects using suds. * Alternative choice implementation that would not automatically expand all choice function parameters but instead take some more generic 'choice' object parameter. * This object would then know which of its data members is 'currently specified'. * See whether the Marshaller class should know about choice elements and not add XML nodes for elements with the value 'None' if they are contained directly inside a choice. * For some more background information on this see Marshaller related release notes & commit messages inherited from the original 'suds' development project. * See if the class 'suds.xsd.sxbasic.Complex' function sequence() should return True if its only child is a sequence node. * Typo corrections for the original project web site. * 'docuemnt' --> 'document'. * 'becuase' --> 'because'. * See whether standard and/or suds supports specifying message part parameter element directly from an external schema. * For example, 'integer' or 'string' elements defined in the 'http://schemas.xmlsoap.org/soap/encoding/' namespace (stored internally by suds in the store.py module). * See whether importing an external WSDL schema can benefit from that schema's WSDL object already being cached. * See if this has already been implemented, and if not - implement it. * Additional test ideas. * External XSD usage (including both imported and included external XSD schemas). * All the different external XSD usage tests should be run in scenarios when the external XSD gets used from an imported WSDL. * Using an external XSD schema using another external XSD schema. * Test pluggable sax character reference encoder support. * Have tests plug in a custom character reference encoder and try it out. * A HttpTransport.send() operation will return None if the web service server responds with HTTP status code ACCEPTED (202) or NO_CONTENT (204), but the _SoapClient method using this operation is not prepared to handle this and will attempt to access the 'message' member on the returned value. * Failing tests have already been added for this. * Alternative ideas on how to fix this. * Continue returning None in these cases and fix up the calling code. * Raise a separate exception. * Wrap this information in a regular suds.transport.Reply instance. * Possibly handle this differently based based on whether the web service operation invoked has any defined output message parts. * 'soapheaders' suds option usage cleanup. * Add tests. * SOAP headers given as a list/tuple. * Strings. * Elements. * Initial. * Middle. * Last. * Multiple. * SOAP headers given as a dictionary. * Strings. * Elements - error. * Unknown header handling. * Too many when given as list/tuple. * Unknown key when given as dictionary. * Plugins modifying SOAP header elements should not affect future requests constructed using those same headers. * Note that Elements found in the soapheaders option need to be copied into each request so any plugins updating that request's content would not update the original SOAP header elements and thus affect all future requests constructed using those same SOAP headers. * Fix bug: Elements added to a soapheaders list before all expected headers have been added, but later ones are ignored. * Raise an exception in case of an unrecognized header. * Unrecognized header name in a dictionary. * Too many non-Element headers in a list/tuple. * Better document the 'soapheaders' suds option. * At first glance it seems suds does not use reply headers the same as it does its request headers. See if SOAP web service HTTP bindings allow output headers at all, and if so, how suds should support this. * See if header elements can be marked as required, and if so, see if suds should report an error if a required header value has not been provided. * See if header elements can be marked as optional and how suds should handle this. * See if header elements can have multiple occurrences, and if so, how suds should support them. * See how header values can be specified more easily for a single method invocation. * See if input message parts defined using the 'type' attribute (as opposed to those marked using the 'element' attribute) respect their optional/ required status. PartElement class used to model such part's element information seems to always return True from its optional() method. * If this is wrong - add a test demonstrating the problem. * If this is correct - see what the suspicious optional() method is for. * Look at suds.sax.element.Element.insert() - its documentation says it accepts either an element or an attribute or a collection of elements or attributes and adds it/them as a child/children under the current element. * From glancing through the code it does not seem to work when passed a collection of elements or attributes. * Most likely it needs a check whether the passed objects parameter is a list or a tuple before it wraps it inside a tuple. * From glancing through the code it does not seems to work when passed an Attribute. * See what to do about this - either update the related function documentation or update it to work as documented. * See how similar functionality has been implemented in suds.sax.element.Element.append(). * Look at suds.sax.element.Element.childAtPath() and see whether it should return None or self if given an empty string or a string consisting only of slashes. * Currently it returns None in this case as if the given path could not be found. * Returning self or raising an exception seems consistent with the fact that a non-empty path is interpreted relative to self. * suds.sax.element.Element.__childrenAtPath() seems smelly. See how it can be simplified and made to work for paths not containing slashes as well as those containing slashes. * This should simplify the suds.sax.element.Element.childrenAtPath() implementation as well. * suds.sax parser peculiarities to look into. * Handling mixed content elements, i.e. those that have both child elements and textual content. * All the textual content is concatenated into a single string, independent of how it distributed between child elements in the original parsed XML string. * As a consequence, converting the parsed XML document back into an XML string will place all the element's textual content before its child elements. * The textual content has its whitespace trimmed. * Note that this causes the ' 42 42 ' XML to be parsed so that element's textual data is: '42 42' where the whitespace outside the '42' markers is trimmed but the whitespace between them is preserved. * See if we should make sure any plugin exceptions get logged correctly (as was the case when suds.plugin.Method.__call__() was eating up all such exception in the original suds project). * The original suds project's DocumentCache.put() implementation accepted suds.sax.element.Element instances as input so we kept this behaviour for now but that might have actually been a result of a bug. We should review this and update related tests & implementation if we no longer wish to support this. * See the TestDocumentCache.test_cache_element() test in 'test_sax_element.py'. * Improve the XML comparison test utilities. * Add test utility tests. * Make SAX comparison compare XML attributes - order is irrelevant. * Support for comparing two XML data strings in more detail, with explicitly enabling/disabling which differences to consider important. * Some ideas on what might or might not be considered important. * Leading/trailing textual data whitespace. * Per element. * Per block between child elements. * Per input line. * All whitespace blocks considered equivalent. * Possibly consider leading/trailing textual data whitespace unimportant only inside mixed content nodes containing at least one child element. * How a mixed content node's textual content is split between its child elements. * Attribute declaration order. * Child element ordering. * Namespace declarations (no prefix). * Namespace prefix declarations. * External references - to parse them or not. * Extra elements on either side. * XML node namespace prefixes. * XML node namespaces. * Input code-page. * Extra data. * Before XML. * Surrounding top level XML nodes. * Each feature importance might be enabled/disabled globally or for a specific XML part only. * Add SOAP 1.2 support. * Related links. * Project's issue #50 on the BitBucket issue tracker: https://bitbucket.org/jurko/suds/issue/50#comment-10940316 * http://stackoverflow.com/questions/2370573/soap-1-2-python-client * Add support for the `mixed` attribute on `complexType` XSD schema elements. * They should allow the use of textual data, possibly in addition to other child elements & attributes. * Related link: https://bitbucket.org/jurko/suds/issue/51#comment-10941116 suds-1.1.2/docs/000077500000000000000000000000001425611400200134145ustar00rootroot00000000000000suds-1.1.2/docs/Makefile000066400000000000000000000167001425611400200150600ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = /usr/bin/sphinx-build PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " epub3 to make an epub3" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" @echo " dummy to check syntax errors of document sources" .PHONY: clean clean: rm -rf $(BUILDDIR)/* .PHONY: html html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." .PHONY: dirhtml dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." .PHONY: singlehtml singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." .PHONY: pickle pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." .PHONY: json json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." .PHONY: htmlhelp htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." .PHONY: qthelp qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Suds.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Suds.qhc" .PHONY: applehelp applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." .PHONY: devhelp devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Suds" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Suds" @echo "# devhelp" .PHONY: epub epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." .PHONY: epub3 epub3: $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 @echo @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." .PHONY: latex latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." .PHONY: latexpdf latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: latexpdfja latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: text text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." .PHONY: man man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." .PHONY: texinfo texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." .PHONY: info info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." .PHONY: gettext gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." .PHONY: changes changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." .PHONY: linkcheck linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." .PHONY: doctest doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." .PHONY: coverage coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." .PHONY: xml xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." .PHONY: pseudoxml pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." .PHONY: dummy dummy: $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy @echo @echo "Build finished. Dummy builder generates no files." suds-1.1.2/docs/README.rst000066400000000000000000000005501425611400200151030ustar00rootroot00000000000000Suds Documentation ================== In order to generate the ``suds`` html documentation you need Sphinx v1.8.5 This folder must be in the root of the repository. At this very same level do: $ make html The documentation will be stored in the 'build' folder. Licensed under LGPL (see the ``LICENSE.txt`` file included in the distribution) suds-1.1.2/docs/make.bat000066400000000000000000000164401425611400200150260ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source set I18NSPHINXOPTS=%SPHINXOPTS% source if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. epub3 to make an epub3 echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled echo. coverage to run coverage check of the documentation if enabled echo. dummy to check syntax errors of document sources goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) REM Check if sphinx-build is available and fallback to Python version if any %SPHINXBUILD% 1>NUL 2>NUL if errorlevel 9009 goto sphinx_python goto sphinx_ok :sphinx_python set SPHINXBUILD=python -m sphinx.__init__ %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) :sphinx_ok if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Suds.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Suds.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "epub3" ( %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %~dp0 echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %~dp0 echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "coverage" ( %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage if errorlevel 1 exit /b 1 echo. echo.Testing of coverage in the sources finished, look at the ^ results in %BUILDDIR%/coverage/python.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) if "%1" == "dummy" ( %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy if errorlevel 1 exit /b 1 echo. echo.Build finished. Dummy builder generates no files. goto end ) :end suds-1.1.2/docs/migrate_docstrings.sh000077500000000000000000000022631425611400200176450ustar00rootroot00000000000000#!/usr/bin/env bash # # Script for migrating from epydoc to Sphinx style docstrings. # # WARNING: THIS SCRIPT MODIFIES FILES IN PLACE. BE SURE TO BACKUP THEM BEFORE # RUNNING IT. DIRECTORY=$1 SED=`which gsed gnused sed` for value in $SED do SED=${value} break done if [ ! $DIRECTORY ]; then echo "Usage: ./migrate_docstrings.sh " exit 1 fi OLD_VALUES[0]='@type' OLD_VALUES[1]='@keyword' OLD_VALUES[2]='@param' OLD_VALUES[3]='@return' OLD_VALUES[4]='@rtype' OLD_VALUES[5]='L{\([^}]\+\)}' OLD_VALUES[6]='C{\(int\|float\|str\|list\|tuple\|dict\|bool\|None\|generator\|object\)}' OLD_VALUES[7]='@\(ivar\|cvar\|var\)' OLD_VALUES[8]='I{\([^}]\+\)}' NEW_VALUES[0]=':type' NEW_VALUES[1]=':keyword' NEW_VALUES[2]=':param' NEW_VALUES[3]=':return' NEW_VALUES[4]=':rtype' NEW_VALUES[5]=':class:`\1`' NEW_VALUES[6]='``\1``' NEW_VALUES[7]=':\1' NEW_VALUES[8]='``\1``' for (( i = 0 ; i < ${#OLD_VALUES[@]} ; i++ )) do old_value=${OLD_VALUES[$i]} new_value=${NEW_VALUES[$i]} cmd="find ${DIRECTORY} -name '*.py' -type f -print0 | xargs -0 ${SED} -i -e 's/${old_value}/${new_value}/g'" echo "Migrating: ${old_value} -> ${new_value}" eval "$cmd" done suds-1.1.2/docs/source/000077500000000000000000000000001425611400200147145ustar00rootroot00000000000000suds-1.1.2/docs/source/README.md000077700000000000000000000000001425611400200200732../../README.mdustar00rootroot00000000000000suds-1.1.2/docs/source/conf.py000066400000000000000000000236421425611400200162220ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Suds documentation build configuration file, created by # sphinx-quickstart on Tue Oct 16 23:45:27 2018. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import sys sys.path.insert(0, os.path.abspath('.')) sys.path.insert(0, os.path.abspath('../')) sys.path.insert(0, os.path.abspath('../..')) sys.path.insert(0, os.path.abspath('../../suds')) import suds # Convert from epydocs to Sphinx style docstrings os.system('../migrate_docstrings.sh ../../suds/') # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.8.5' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.githubpages', 'sphinx.ext.napoleon', 'sphinx.ext.intersphinx', 'recommonmark' ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] # The encoding of source files. # # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Suds' copyright = u'2020, Jurko Gospodnetić/suds-community' author = u'Jurko Gospodnetić/suds-community' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = suds.__version__ # The full version, including alpha/beta/rc tags. release = suds.__version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # # today = '' # # Else, today_fmt is used as the format for a strftime call. # # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. # " v documentation" by default. # # html_title = u'Suds v0.7.1' # A shorter title for the navigation bar. Default is the same as html_title. # # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # # html_logo = None # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # # html_extra_path = [] # If not None, a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. # The empty string is equivalent to '%b %d, %Y'. # # html_last_updated_fmt = None # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # # html_additional_pages = {} # If false, no module index is generated. # # html_domain_indices = True # If false, no index is generated. # # html_use_index = True # If true, the index is split into individual pages for each letter. # # html_split_index = False # If true, links to the reST sources are added to the pages. # # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = False # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' # # html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # 'ja' uses this config value. # 'zh' user can custom change `jieba` dictionary path. # # html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. # # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'Sudsdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'Suds.tex', u'Suds Documentation', u'Jurko Gospodnetić/suds-community', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # # latex_use_parts = False # If true, show page references after internal links. # # latex_show_pagerefs = False # If true, show URL addresses after external links. # # latex_show_urls = False # Documents to append as an appendix to all manuals. # # latex_appendices = [] # It false, will not define \strong, \code, itleref, \crossref ... but only # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added # packages. # # latex_keep_old_macro_names = True # If false, no module index is generated. # # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'suds', u'Suds Documentation', [author], 1) ] # If true, show URL addresses after external links. # # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'Suds', u'Suds Documentation', author, 'Suds', 'Lightweight SOAP client (community fork)', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # # texinfo_appendices = [] # If false, no module index is generated. # # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # # texinfo_no_detailmenu = False suds-1.1.2/docs/source/index.rst000066400000000000000000000007121425611400200165550ustar00rootroot00000000000000.. Suds documentation master file, created by sphinx-quickstart on Tue Oct 16 23:45:27 2018. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to Suds's documentation! ================================ Contents: .. toctree:: :maxdepth: 2 Overview modules.rst Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` suds-1.1.2/docs/source/modules.rst000066400000000000000000000000611425611400200171130ustar00rootroot00000000000000suds ==== .. toctree:: :maxdepth: 4 suds suds-1.1.2/docs/source/suds.bindings.rst000066400000000000000000000014431425611400200202220ustar00rootroot00000000000000suds.bindings package ===================== Submodules ---------- suds.bindings.binding module ---------------------------- .. automodule:: suds.bindings.binding :members: :undoc-members: :show-inheritance: suds.bindings.document module ----------------------------- .. automodule:: suds.bindings.document :members: :undoc-members: :show-inheritance: suds.bindings.multiref module ----------------------------- .. automodule:: suds.bindings.multiref :members: :undoc-members: :show-inheritance: suds.bindings.rpc module ------------------------ .. automodule:: suds.bindings.rpc :members: :undoc-members: :show-inheritance: Module contents --------------- .. automodule:: suds.bindings :members: :undoc-members: :show-inheritance: suds-1.1.2/docs/source/suds.mx.rst000066400000000000000000000017171425611400200170550ustar00rootroot00000000000000suds.mx package =============== Submodules ---------- suds.mx.appender module ----------------------- .. automodule:: suds.mx.appender :members: :undoc-members: :show-inheritance: suds.mx.basic module -------------------- .. automodule:: suds.mx.basic :members: :undoc-members: :show-inheritance: suds.mx.core module ------------------- .. automodule:: suds.mx.core :members: :undoc-members: :show-inheritance: suds.mx.encoded module ---------------------- .. automodule:: suds.mx.encoded :members: :undoc-members: :show-inheritance: suds.mx.literal module ---------------------- .. automodule:: suds.mx.literal :members: :undoc-members: :show-inheritance: suds.mx.typer module -------------------- .. automodule:: suds.mx.typer :members: :undoc-members: :show-inheritance: Module contents --------------- .. automodule:: suds.mx :members: :undoc-members: :show-inheritance: suds-1.1.2/docs/source/suds.rst000066400000000000000000000051111425611400200164220ustar00rootroot00000000000000suds package ============ Subpackages ----------- .. toctree:: suds.bindings suds.mx suds.sax suds.transport suds.umx suds.xsd Submodules ---------- suds.argparser module --------------------- .. automodule:: suds.argparser :members: :undoc-members: :show-inheritance: suds.builder module ------------------- .. automodule:: suds.builder :members: :undoc-members: :show-inheritance: suds.cache module ----------------- .. automodule:: suds.cache :members: :undoc-members: :show-inheritance: suds.client module ------------------ .. automodule:: suds.client :members: :undoc-members: :show-inheritance: suds.metrics module ------------------- .. automodule:: suds.metrics :members: :undoc-members: :show-inheritance: suds.options module ------------------- .. automodule:: suds.options :members: :undoc-members: :show-inheritance: suds.plugin module ------------------ .. automodule:: suds.plugin :members: :undoc-members: :show-inheritance: suds.properties module ---------------------- .. automodule:: suds.properties :members: :undoc-members: :show-inheritance: suds.reader module ------------------ .. automodule:: suds.reader :members: :undoc-members: :show-inheritance: suds.resolver module -------------------- .. automodule:: suds.resolver :members: :undoc-members: :show-inheritance: suds.servicedefinition module ----------------------------- .. automodule:: suds.servicedefinition :members: :undoc-members: :show-inheritance: suds.serviceproxy module ------------------------ .. automodule:: suds.serviceproxy :members: :undoc-members: :show-inheritance: suds.soaparray module --------------------- .. automodule:: suds.soaparray :members: :undoc-members: :show-inheritance: suds.store module ----------------- .. automodule:: suds.store :members: :undoc-members: :show-inheritance: suds.sudsobject module ---------------------- .. automodule:: suds.sudsobject :members: :undoc-members: :show-inheritance: suds.version module ------------------- .. automodule:: suds.version :members: :undoc-members: :show-inheritance: suds.wsdl module ---------------- .. automodule:: suds.wsdl :members: :undoc-members: :show-inheritance: suds.wsse module ---------------- .. automodule:: suds.wsse :members: :undoc-members: :show-inheritance: Module contents --------------- .. automodule:: suds :members: :undoc-members: :show-inheritance: suds-1.1.2/docs/source/suds.sax.rst000066400000000000000000000021521425611400200172160ustar00rootroot00000000000000suds.sax package ================ Submodules ---------- suds.sax.attribute module ------------------------- .. automodule:: suds.sax.attribute :members: :undoc-members: :show-inheritance: suds.sax.date module -------------------- .. automodule:: suds.sax.date :members: :undoc-members: :show-inheritance: suds.sax.document module ------------------------ .. automodule:: suds.sax.document :members: :undoc-members: :show-inheritance: suds.sax.element module ----------------------- .. automodule:: suds.sax.element :members: :undoc-members: :show-inheritance: suds.sax.enc module ------------------- .. automodule:: suds.sax.enc :members: :undoc-members: :show-inheritance: suds.sax.parser module ---------------------- .. automodule:: suds.sax.parser :members: :undoc-members: :show-inheritance: suds.sax.text module -------------------- .. automodule:: suds.sax.text :members: :undoc-members: :show-inheritance: Module contents --------------- .. automodule:: suds.sax :members: :undoc-members: :show-inheritance: suds-1.1.2/docs/source/suds.transport.rst000066400000000000000000000012131425611400200204540ustar00rootroot00000000000000suds.transport package ====================== Submodules ---------- suds.transport.http module -------------------------- .. automodule:: suds.transport.http :members: :undoc-members: :show-inheritance: suds.transport.https module --------------------------- .. automodule:: suds.transport.https :members: :undoc-members: :show-inheritance: suds.transport.options module ----------------------------- .. automodule:: suds.transport.options :members: :undoc-members: :show-inheritance: Module contents --------------- .. automodule:: suds.transport :members: :undoc-members: :show-inheritance: suds-1.1.2/docs/source/suds.umx.rst000066400000000000000000000015301425611400200172330ustar00rootroot00000000000000suds.umx package ================ Submodules ---------- suds.umx.attrlist module ------------------------ .. automodule:: suds.umx.attrlist :members: :undoc-members: :show-inheritance: suds.umx.basic module --------------------- .. automodule:: suds.umx.basic :members: :undoc-members: :show-inheritance: suds.umx.core module -------------------- .. automodule:: suds.umx.core :members: :undoc-members: :show-inheritance: suds.umx.encoded module ----------------------- .. automodule:: suds.umx.encoded :members: :undoc-members: :show-inheritance: suds.umx.typed module --------------------- .. automodule:: suds.umx.typed :members: :undoc-members: :show-inheritance: Module contents --------------- .. automodule:: suds.umx :members: :undoc-members: :show-inheritance: suds-1.1.2/docs/source/suds.xsd.rst000066400000000000000000000021711425611400200172220ustar00rootroot00000000000000suds.xsd package ================ Submodules ---------- suds.xsd.depsort module ----------------------- .. automodule:: suds.xsd.depsort :members: :undoc-members: :show-inheritance: suds.xsd.doctor module ---------------------- .. automodule:: suds.xsd.doctor :members: :undoc-members: :show-inheritance: suds.xsd.query module --------------------- .. automodule:: suds.xsd.query :members: :undoc-members: :show-inheritance: suds.xsd.schema module ---------------------- .. automodule:: suds.xsd.schema :members: :undoc-members: :show-inheritance: suds.xsd.sxbase module ---------------------- .. automodule:: suds.xsd.sxbase :members: :undoc-members: :show-inheritance: suds.xsd.sxbasic module ----------------------- .. automodule:: suds.xsd.sxbasic :members: :undoc-members: :show-inheritance: suds.xsd.sxbuiltin module ------------------------- .. automodule:: suds.xsd.sxbuiltin :members: :undoc-members: :show-inheritance: Module contents --------------- .. automodule:: suds.xsd :members: :undoc-members: :show-inheritance: suds-1.1.2/ez_setup.py000066400000000000000000000243521425611400200147020ustar00rootroot00000000000000#!/usr/bin/env python """Bootstrap setuptools installation To use setuptools in your package's setup.py, include this file in the same directory and add this to the top of your setup.py:: from ez_setup import use_setuptools use_setuptools() To require a specific version of setuptools, set a download mirror, or use an alternate download directory, simply supply the appropriate options to ``use_setuptools()``. This file can also be run as a script to install or upgrade setuptools. """ import os import shutil import sys import tempfile import zipfile import optparse import subprocess import platform import textwrap import contextlib from distutils import log try: from urllib.request import urlopen except ImportError: from urllib2 import urlopen try: from site import USER_SITE except ImportError: USER_SITE = None DEFAULT_VERSION = "5.1" DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/" def _python_cmd(*args): """ Return True if the command succeeded. """ args = (sys.executable,) + args return subprocess.call(args) == 0 def _install(archive_filename, install_args=()): with archive_context(archive_filename): # installing log.warn('Installing Setuptools') if not _python_cmd('setup.py', 'install', *install_args): log.warn('Something went wrong during the installation.') log.warn('See the error message above.') # exitcode will be 2 return 2 def _build_egg(egg, archive_filename, to_dir): with archive_context(archive_filename): # building an egg log.warn('Building a Setuptools egg in %s', to_dir) _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) # returning the result log.warn(egg) if not os.path.exists(egg): raise IOError('Could not build the egg.') class ContextualZipFile(zipfile.ZipFile): """ Supplement ZipFile class to support context manager for Python 2.6 """ def __enter__(self): return self def __exit__(self, type, value, traceback): self.close() def __new__(cls, *args, **kwargs): """ Construct a ZipFile or ContextualZipFile as appropriate """ if hasattr(zipfile.ZipFile, '__exit__'): return zipfile.ZipFile(*args, **kwargs) return super(ContextualZipFile, cls).__new__(cls) @contextlib.contextmanager def archive_context(filename): # extracting the archive tmpdir = tempfile.mkdtemp() log.warn('Extracting in %s', tmpdir) old_wd = os.getcwd() try: os.chdir(tmpdir) with ContextualZipFile(filename) as archive: archive.extractall() # going in the directory subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) os.chdir(subdir) log.warn('Now working in %s', subdir) yield finally: os.chdir(old_wd) shutil.rmtree(tmpdir) def _do_download(version, download_base, to_dir, download_delay): egg = os.path.join(to_dir, 'setuptools-%s-py%d.%d.egg' % (version, sys.version_info[0], sys.version_info[1])) if not os.path.exists(egg): archive = download_setuptools(version, download_base, to_dir, download_delay) _build_egg(egg, archive, to_dir) sys.path.insert(0, egg) # Remove previously-imported pkg_resources if present (see # https://bitbucket.org/pypa/setuptools/pull-request/7/ for details). if 'pkg_resources' in sys.modules: del sys.modules['pkg_resources'] import setuptools setuptools.bootstrap_install_from = egg def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, download_delay=15): to_dir = os.path.abspath(to_dir) rep_modules = 'pkg_resources', 'setuptools' imported = set(sys.modules).intersection(rep_modules) try: import pkg_resources except ImportError: return _do_download(version, download_base, to_dir, download_delay) try: pkg_resources.require("setuptools>=" + version) return except pkg_resources.DistributionNotFound: return _do_download(version, download_base, to_dir, download_delay) except pkg_resources.VersionConflict as VC_err: if imported: msg = textwrap.dedent(""" The required version of setuptools (>={version}) is not available, and can't be installed while this script is running. Please install a more recent version first, using 'easy_install -U setuptools'. (Currently using {VC_err.args[0]!r}) """).format(VC_err=VC_err, version=version) sys.stderr.write(msg) sys.exit(2) # otherwise, reload ok del pkg_resources, sys.modules['pkg_resources'] return _do_download(version, download_base, to_dir, download_delay) def _clean_check(cmd, target): """ Run the command to download target. If the command fails, clean up before re-raising the error. """ try: subprocess.check_call(cmd) except subprocess.CalledProcessError: if os.access(target, os.F_OK): os.unlink(target) raise def download_file_powershell(url, target): """ Download the file at url to target using Powershell (which will validate trust). Raise an exception if the command cannot complete. """ target = os.path.abspath(target) ps_cmd = ( "[System.Net.WebRequest]::DefaultWebProxy.Credentials = " "[System.Net.CredentialCache]::DefaultCredentials; " "(new-object System.Net.WebClient).DownloadFile(%(url)r, %(target)r)" % vars() ) cmd = [ 'powershell', '-Command', ps_cmd, ] _clean_check(cmd, target) def has_powershell(): if platform.system() != 'Windows': return False cmd = ['powershell', '-Command', 'echo test'] with open(os.path.devnull, 'wb') as devnull: try: subprocess.check_call(cmd, stdout=devnull, stderr=devnull) except Exception: return False return True download_file_powershell.viable = has_powershell def download_file_curl(url, target): cmd = ['curl', url, '--silent', '--output', target] _clean_check(cmd, target) def has_curl(): cmd = ['curl', '--version'] with open(os.path.devnull, 'wb') as devnull: try: subprocess.check_call(cmd, stdout=devnull, stderr=devnull) except Exception: return False return True download_file_curl.viable = has_curl def download_file_wget(url, target): cmd = ['wget', url, '--quiet', '--output-document', target] _clean_check(cmd, target) def has_wget(): cmd = ['wget', '--version'] with open(os.path.devnull, 'wb') as devnull: try: subprocess.check_call(cmd, stdout=devnull, stderr=devnull) except Exception: return False return True download_file_wget.viable = has_wget def download_file_insecure(url, target): """ Use Python to download the file, even though it cannot authenticate the connection. """ src = urlopen(url) try: # Read all the data in one block. data = src.read() finally: src.close() # Write all the data in one block to avoid creating a partial file. with open(target, "wb") as dst: dst.write(data) download_file_insecure.viable = lambda: True def get_best_downloader(): downloaders = ( download_file_powershell, download_file_curl, download_file_wget, download_file_insecure, ) viable_downloaders = (dl for dl in downloaders if dl.viable()) return next(viable_downloaders, None) def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, delay=15, downloader_factory=get_best_downloader): """ Download setuptools from a specified location and return its filename `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where the egg will be downloaded. `delay` is the number of seconds to pause before an actual download attempt. ``downloader_factory`` should be a function taking no arguments and returning a function for downloading a URL to a target. """ # making sure we use the absolute path to_dir = os.path.abspath(to_dir) zip_name = "setuptools-%s.zip" % version url = download_base + zip_name saveto = os.path.join(to_dir, zip_name) if not os.path.exists(saveto): # Avoid repeated downloads log.warn("Downloading %s", url) downloader = downloader_factory() downloader(url, saveto) return os.path.realpath(saveto) def _build_install_args(options): """ Build the arguments to 'python setup.py install' on the setuptools package """ return ['--user'] if options.user_install else [] def _parse_args(): """ Parse the command line for options """ parser = optparse.OptionParser() parser.add_option( '--user', dest='user_install', action='store_true', default=False, help='install in user site package (requires Python 2.6 or later)') parser.add_option( '--download-base', dest='download_base', metavar="URL", default=DEFAULT_URL, help='alternative URL from where to download the setuptools package') parser.add_option( '--insecure', dest='downloader_factory', action='store_const', const=lambda: download_file_insecure, default=get_best_downloader, help='Use internal, non-validating downloader' ) parser.add_option( '--version', help="Specify which version to download", default=DEFAULT_VERSION, ) options, args = parser.parse_args() # positional arguments are ignored return options def main(): """Install or upgrade setuptools and EasyInstall""" options = _parse_args() archive = download_setuptools( version=options.version, download_base=options.download_base, downloader_factory=options.downloader_factory, ) return _install(archive, _build_install_args(options)) if __name__ == '__main__': sys.exit(main()) suds-1.1.2/ez_setup_1_4_2.py000066400000000000000000000301371425611400200155640ustar00rootroot00000000000000#!python """Bootstrap setuptools installation If you want to use setuptools in your package's setup.py, just include this file in the same directory with it, and add this to the top of your setup.py:: from ez_setup import use_setuptools use_setuptools() If you want to require a specific version of setuptools, set a download mirror, or use an alternate download directory, you can do so by supplying the appropriate options to ``use_setuptools()``. This file can also be run as a script to install or upgrade setuptools. """ import os import shutil import sys import tempfile import tarfile import optparse import subprocess import platform from distutils import log try: from site import USER_SITE except ImportError: USER_SITE = None DEFAULT_VERSION = "1.4.2" DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/" def _python_cmd(*args): args = (sys.executable,) + args return subprocess.call(args) == 0 def _check_call_py24(cmd, *args, **kwargs): res = subprocess.call(cmd, *args, **kwargs) class CalledProcessError(Exception): pass if not res == 0: msg = "Command '%s' return non-zero exit status %d" % (cmd, res) raise CalledProcessError(msg) vars(subprocess).setdefault('check_call', _check_call_py24) def _install(tarball, install_args=()): # extracting the tarball tmpdir = tempfile.mkdtemp() log.warn('Extracting in %s', tmpdir) old_wd = os.getcwd() try: os.chdir(tmpdir) tar = tarfile.open(tarball) _extractall(tar) tar.close() # going in the directory subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) os.chdir(subdir) log.warn('Now working in %s', subdir) # installing log.warn('Installing Setuptools') if not _python_cmd('setup.py', 'install', *install_args): log.warn('Something went wrong during the installation.') log.warn('See the error message above.') # exitcode will be 2 return 2 finally: os.chdir(old_wd) shutil.rmtree(tmpdir) def _build_egg(egg, tarball, to_dir): # extracting the tarball tmpdir = tempfile.mkdtemp() log.warn('Extracting in %s', tmpdir) old_wd = os.getcwd() try: os.chdir(tmpdir) tar = tarfile.open(tarball) _extractall(tar) tar.close() # going in the directory subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) os.chdir(subdir) log.warn('Now working in %s', subdir) # building an egg log.warn('Building a Setuptools egg in %s', to_dir) _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) finally: os.chdir(old_wd) shutil.rmtree(tmpdir) # returning the result log.warn(egg) if not os.path.exists(egg): raise IOError('Could not build the egg.') def _do_download(version, download_base, to_dir, download_delay): egg = os.path.join(to_dir, 'setuptools-%s-py%d.%d.egg' % (version, sys.version_info[0], sys.version_info[1])) if not os.path.exists(egg): tarball = download_setuptools(version, download_base, to_dir, download_delay) _build_egg(egg, tarball, to_dir) sys.path.insert(0, egg) # Remove previously-imported pkg_resources if present (see # https://bitbucket.org/pypa/setuptools/pull-request/7/ for details). if 'pkg_resources' in sys.modules: del sys.modules['pkg_resources'] import setuptools setuptools.bootstrap_install_from = egg def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, download_delay=15): # making sure we use the absolute path to_dir = os.path.abspath(to_dir) was_imported = 'pkg_resources' in sys.modules or \ 'setuptools' in sys.modules try: import pkg_resources except ImportError: return _do_download(version, download_base, to_dir, download_delay) try: pkg_resources.require("setuptools>=" + version) return except pkg_resources.VersionConflict: e = sys.exc_info()[1] if was_imported: sys.stderr.write( "The required version of setuptools (>=%s) is not available,\n" "and can't be installed while this script is running. Please\n" "install a more recent version first, using\n" "'easy_install -U setuptools'." "\n\n(Currently using %r)\n" % (version, e.args[0])) sys.exit(2) else: del pkg_resources, sys.modules['pkg_resources'] # reload ok return _do_download(version, download_base, to_dir, download_delay) except pkg_resources.DistributionNotFound: return _do_download(version, download_base, to_dir, download_delay) def _clean_check(cmd, target): """ Run the command to download target. If the command fails, clean up before re-raising the error. """ try: subprocess.check_call(cmd) except subprocess.CalledProcessError: if os.access(target, os.F_OK): os.unlink(target) raise def download_file_powershell(url, target): """ Download the file at url to target using Powershell (which will validate trust). Raise an exception if the command cannot complete. """ target = os.path.abspath(target) cmd = [ 'powershell', '-Command', "(new-object System.Net.WebClient).DownloadFile(%(url)r, %(target)r)" % vars(), ] _clean_check(cmd, target) def has_powershell(): if platform.system() != 'Windows': return False cmd = ['powershell', '-Command', 'echo test'] devnull = open(os.path.devnull, 'wb') try: try: subprocess.check_call(cmd, stdout=devnull, stderr=devnull) except (KeyboardInterrupt, SystemExit): raise except Exception: return False finally: devnull.close() return True download_file_powershell.viable = has_powershell def download_file_curl(url, target): cmd = ['curl', url, '--silent', '--output', target] _clean_check(cmd, target) def has_curl(): cmd = ['curl', '--version'] devnull = open(os.path.devnull, 'wb') try: try: subprocess.check_call(cmd, stdout=devnull, stderr=devnull) except (KeyboardInterrupt, SystemExit): raise except Exception: return False finally: devnull.close() return True download_file_curl.viable = has_curl def download_file_wget(url, target): cmd = ['wget', url, '--quiet', '--output-document', target] _clean_check(cmd, target) def has_wget(): cmd = ['wget', '--version'] devnull = open(os.path.devnull, 'wb') try: try: subprocess.check_call(cmd, stdout=devnull, stderr=devnull) except (KeyboardInterrupt, SystemExit): raise except Exception: return False finally: devnull.close() return True download_file_wget.viable = has_wget def download_file_insecure(url, target): """ Use Python to download the file, even though it cannot authenticate the connection. """ try: from urllib.request import urlopen except ImportError: from urllib2 import urlopen src = dst = None try: src = urlopen(url) # Read/write all in one block, so we don't create a corrupt file # if the download is interrupted. data = src.read() dst = open(target, "wb") dst.write(data) finally: if src: src.close() if dst: dst.close() download_file_insecure.viable = lambda: True def get_best_downloader(): downloaders = [ download_file_powershell, download_file_curl, download_file_wget, download_file_insecure, ] for dl in downloaders: if dl.viable(): return dl def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, delay=15, downloader_factory=get_best_downloader): """Download setuptools from a specified location and return its filename `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where the egg will be downloaded. `delay` is the number of seconds to pause before an actual download attempt. ``downloader_factory`` should be a function taking no arguments and returning a function for downloading a URL to a target. """ # making sure we use the absolute path to_dir = os.path.abspath(to_dir) tgz_name = "setuptools-%s.tar.gz" % version url = download_base + tgz_name saveto = os.path.join(to_dir, tgz_name) if not os.path.exists(saveto): # Avoid repeated downloads log.warn("Downloading %s", url) downloader = downloader_factory() downloader(url, saveto) return os.path.realpath(saveto) def _extractall(self, path=".", members=None): """Extract all members from the archive to the current working directory and set owner, modification time and permissions on directories afterwards. `path' specifies a different directory to extract to. `members' is optional and must be a subset of the list returned by getmembers(). """ import copy import operator from tarfile import ExtractError directories = [] if members is None: members = self for tarinfo in members: if tarinfo.isdir(): # Extract directories with a safe mode. directories.append(tarinfo) tarinfo = copy.copy(tarinfo) tarinfo.mode = 448 # decimal for oct 0700 self.extract(tarinfo, path) # Reverse sort directories. if sys.version_info < (2, 4): def sorter(dir1, dir2): return cmp(dir1.name, dir2.name) directories.sort(sorter) directories.reverse() else: directories.sort(key=operator.attrgetter('name'), reverse=True) # Set correct owner, mtime and filemode on directories. for tarinfo in directories: dirpath = os.path.join(path, tarinfo.name) try: self.chown(tarinfo, dirpath) self.utime(tarinfo, dirpath) self.chmod(tarinfo, dirpath) except ExtractError: e = sys.exc_info()[1] if self.errorlevel > 1: raise else: self._dbg(1, "tarfile: %s" % e) def _build_install_args(options): """ Build the arguments to 'python setup.py install' on the setuptools package """ install_args = [] if options.user_install: if sys.version_info < (2, 6): log.warn("--user requires Python 2.6 or later") raise SystemExit(1) install_args.append('--user') return install_args def _parse_args(): """ Parse the command line for options """ parser = optparse.OptionParser() parser.add_option( '--user', dest='user_install', action='store_true', default=False, help='install in user site package (requires Python 2.6 or later)') parser.add_option( '--download-base', dest='download_base', metavar="URL", default=DEFAULT_URL, help='alternative URL from where to download the setuptools package') parser.add_option( '--insecure', dest='downloader_factory', action='store_const', const=lambda: download_file_insecure, default=get_best_downloader, help='Use internal, non-validating downloader' ) options, args = parser.parse_args() # positional arguments are ignored return options def main(version=DEFAULT_VERSION): """Install or upgrade setuptools and EasyInstall""" options = _parse_args() tarball = download_setuptools(download_base=options.download_base, downloader_factory=options.downloader_factory) return _install(tarball, _build_install_args(options)) if __name__ == '__main__': sys.exit(main()) suds-1.1.2/notes/000077500000000000000000000000001425611400200136145ustar00rootroot00000000000000suds-1.1.2/notes/argument_parsing.rst000066400000000000000000000153631425611400200177230ustar00rootroot00000000000000=================================================================== Notes on input argument usage when invoking a web service operation =================================================================== :Authors: Jurko Gospodnetić :Date: 2014-01-23 =========== Definitions =========== message parts WSDL schema defines message parts representing a web service operation's input data. Each such part's data structure is defined by mapping the message part to XSD element or type. input parameters Suds library's view of a web service operation's input data. Each message part may correspond to 1 or more input parameters. See the `Bare/wrapped web service operation input parameters`_ section for more detailed information. input arguments Values passed to suds library's Python function or function object invoking a web service operation. Suds attempts to map each input argument to a single web service operation input parameter. =================================================== Bare/wrapped web service operation input parameters =================================================== When suds library models a web service operation, it can be configured to map each of its message parts to a single input parameter expecting values directly matching the data type defined for that message part in the web service's WSDL schema. Such input parameters are called ``bare``. If a particular message part has a type which is actually a structure containing a collection of simpler elements, then suds may be configured to map each of those simpler elements to a single input parameter. Such input parameters are called 'wrapped'. If an input parameter is represented by structured XSD element containing other elements, suds may treat it as either ``bare`` or ``wrapped``. If it is considered ``wrapped``, then suds library's web service operation invocation function will take values for the input parameter's internal elements as input arguments, instead of taking only a single wrapper object as a value for the external wrapper element. ``wrapped`` input parameter support has been implemented to make the interface simpler for the user/programmer using suds to invoke web service operations. It does not affect how the the resulting web service operation invocation request is constructed, i.e. passing a suds object as a single ``bare`` input parameter value, or passing matching contained element values as separate wrapped input parameter values results in the same web service operation invocation request being constructed. Example: -------- Consider an operation taking the following element as its only message part:: Suds may be configured to map that message part into with a single ``bare`` or three ``wrapped`` input parameters. If a ``bare`` input parameter is used, the operation invocation function would take only a single argument: * a suds object argument for element ``unga`` If ``wrapped`` input parameters are used, the operation invocation function would take the following input arguments: * a string argument for element ``a`` * an integer argument for element ``b`` * a suds object argument for element ``c`` ================================================================== Input parameter values in original and current suds implementation ================================================================== * A user may or may not specify a value for a specific input parameter. * We refer to unspecified or ``None`` input parameter values as `undefined`. * We refer to all other input parameter values `defined`. Original suds library implementation: ------------------------------------- * An element may be explicitly marked as optional. * ``choice`` input parameter structures not supported. * A defined input parameter value is used directly. * An undefined optional input parameter value is ignored. * An undefined non-optional input parameter value is interpreted as an empty string. * Multiple values specified for a single input parameter are ignored. * Extra input arguments are ignored. Defects: * ``choice`` input parameter structures not supported correctly. When used, result in incorrectly constructed web service operation invocation requests. * An ``all``/``choice``/``sequence`` input parameter structure may be explicitly marked as optional, but this is ignored when deciding whether a specific input parameter inside that structure is optional or not. * No error when multiple values are specified for a single input parameter. * No error on extra input arguments. Current suds library implementation: ------------------------------------ * An element may be explicitly marked as optional. * ``choice`` input parameter structures supported. * Input parameters contained inside a ``choice`` input parameter structure (either directly or indirectly) are always considered optional. * An input parameter structure containing at least one input parameter with a defined value (either directly or indirectly) is considered to have a defined value. * A defined input parameter value is used directly. * An undefined optional input parameter value is ignored. * An undefined non-optional input parameters value is treated as an empty string. Configurable features: * Multiple values specified for a single input parameter may be reported as an error. * ``choice`` input parameter structure directly containing multiple input parameters and/or input parameter structures with defined values may be reported as an error. * Extra input argument may be reported as an error. Defects (demonstrated by existing unit tests): * An ``all``/``choice``/``sequence`` input parameter structure may be explicitly marked as optional, but this is ignored when deciding whether a specific input parameter inside that structure is optional or not. * Undefined value for a non-optional input parameter contained directly inside an ``all``/``sequence`` input parameter structure contained inside a ``choice`` input parameter structure should be treated as an empty string if the ``all``/``sequence`` input parameter structure has a defined value. * A ``choice`` input parameter structure directly containing an input parameter structure with no elements should be considered optional. Still missing features: ----------------------- * Non-optional ``choice`` input parameter structure with no defined value should be treated as if its first input parameter's value had been specified as an empty string. suds-1.1.2/notes/readme.txt000066400000000000000000000010451425611400200156120ustar00rootroot00000000000000This documentation has been started because we needed some place to put our design, research & development related notes collected collected while hacking on this project. The official suds project documentation has not been included in this fork, as it was not stored in the same repository as the original project's code base. Once official suds project documentation has been integrated into this fork, parts of this documentation that start getting some well rounded form, should be integrated into the official project documentation. suds-1.1.2/notes/traversing_client_data.rst000066400000000000000000000016261425611400200210660ustar00rootroot00000000000000================================================ Examples on traversing suds library's data model ================================================ :Authors: Jurko Gospodnetić :Date: 2014-01-23 Get service from client:: service = client.service Get client from service (for debugging purposes only):: client = service._ServiceSelector__client Get XSD schema information from client:: schema = client.wsdl.schema schema.root # root schema XML element schema.all # all of the schema's imported direct child objects (model) schema.children # all of the schema's direct child objects (model) schema.elements # (name, namespace) --> top level element mapping (model) schema.types # (name, namespace) --> top level type mapping (model) Get XSD schema model object's direct children (i.e. elements, sequences, choices, etc.):: schema_object.rawchildren suds-1.1.2/notes/wsdl_soap_binding.rst000066400000000000000000000055771425611400200200510ustar00rootroot00000000000000================================ WSDL SOAP binding research notes ================================ :Authors: Jurko Gospodnetić :Date: 2014-06-30 WS-I Basic Profile notes ------------------------ Operations defined using the document/literal SOAP binding style should use at most one message part and, if such a message part is defined, it should reference an existing top-level XSD schema element and not a type. There are different interpretations as to what to do about SOAP web service operations that do not comply with WS-I Basic Profile recommendations and either: * use document/literal binding style with multiple message parts * use document/literal binding style operations with a message part referencing an XSD type WSDL message parts referencing an XSD type ------------------------------------------ When a WSDL message part references an XSD type and not an element, as in:: as opposed to:: then it acts as an XSD element of that type, as if it referenced an actual top-level XSD element defined in the XSD schema. XML namespace for SOAP message tags corresponding to a WSDL message part ------------------------------------------------------------------------ If a WSDL message part references an actual XSD element then the namespace is defined by the XSD element's ``target namespace`` property and by whether the element is considered qualified or not. If a WSDL message part references an XSD type, then we have not been able to find a clear standard specification stating what namespace corresponding SOAP message tags should be qualified with. Since such WSDL message parts do not live inside a specific XSD schema, there are no schema ``targetNamespace`` or ``elementFormDefault`` attributes to consult, and since it does not have a ``form`` attribute, there does not seem to be a way to explicitly state whether and which namespace its instances should be qualified with. There seem to be several options here on how to qualify the corresponding SOAP message tags: * with the WSDL schema's target namespace * with the WSDL schema's default namespace * with no namespace Both SoapUI (checked using versions 4.6.1 & 5.0.0) & the original suds implementation choose to use the 'no namespace' version in this case. Some other sources suggest different handling for single-part messages whose operations use document/literal SOAP binding and whose single message part references an XSD type - simply using the type to define the SOAP envelope's ```` element structure instead of adding an additional wrapper element beneath it. Note though that this usage contradicts WS-I Basic Profile recommendation ``R2204``. suds-1.1.2/notes/xsd_elements.rst000066400000000000000000000041771425611400200170510ustar00rootroot00000000000000========================== XSD element research notes ========================== :Authors: Jurko Gospodnetić :Date: 2014-06-29 Global/local elements --------------------- XSD schema elements are separated into two disjunct groups: _`global` top-level elements and non-top-level reference elements (such references may only reference top-level elements, whether from the same or another schema) _`local` non-top-level non-reference elements _`Qualified`/_`unqualified` elements ------------------------------------ An XSD element is considered qualified if and only if one of the following holds: * it is `global`_ * its ``form`` attribute is set to ``qualified`` * its ``form`` attribute is unset and its schema's ``elementFormDefault`` attribute is set to ``qualified`` **suds implementation note:** The only allowed ``form`` & ``elementFormDefault`` attribute values are ``qualified`` & ``unqualified`` but ``suds`` interprets any value other than ``qualified`` as ``unqualified``. Element's _`target namespace` ----------------------------- An XSD element may have a `target namespace`_ assigned. If an XSD element has a `target namespace`_ assigned then an XML element corresponding to this XSD element must belong to that namespace. If an XSD element does not have a `target namespace`_ assigned then an XML element corresponding to this XSD element must not belong to any namespace. Whether an XSD element has a `target namespace`_ assigned depends on whether it is `qualified`_ or not, whether it is a reference and on its or its referenced element schema's ``targetNamespace`` attribute: * an `unqualified`_ element never has a `target namespace`_ assigned * a non-reference `qualified`_ element collects its `target namespace`_ from its schema's non-empty ``targetNamespace`` attribute or has no `target namespace`_ if its schema does not have a non-empty ``targetNamespace`` attribute value specified * a reference element (such elements are always `qualified`_) collects its `target namespace`_ in the same way but used its referenced element schema instead of its own suds-1.1.2/notes/xsd_types.rst000066400000000000000000000025721425611400200163760ustar00rootroot00000000000000========================== Modeling XSD types in suds ========================== :Authors: Jurko Gospodnetić :Date: 2014-01-29 XSD types can be one of the following: built-in Defined in the XSD specification. user defined Constructed by composing built-in and other user defined types according to rules defined in the XSD specification. XSD elements represent specific input/output data in suds. Each XSD element is of a particular XSD type. In suds an XSD element can be *resolved* - process returning an object representing the element's type. Built-in types are represented in suds using different ``XType`` classes defined in the ``suds.xsd.sxbuiltin module``, e.g. ``XFloat``, ``XInteger`` or ``XString``. Such classes define their ``translate()`` methods, implementing transformations between Python objects and their XSD type value representations, i.e. their representation as used in SOAP XML documents. User code can defined additional or replacement ``XType`` classes and register them with suds for a specific XSD type using the ``suds.xsd.sxbuiltin.Factory.maptag()`` method. This way suds can be extended with additional functionality. Example: -------- You can define a replacement ``XFloat`` implementation allowing you to pass a ``fractions.Fraction`` arguments to a web service operation taking an ``xsd:float`` input parameter. suds-1.1.2/setup.cfg000066400000000000000000000072671425611400200143210ustar00rootroot00000000000000# ------------------------------------------------------------------------------ # # Main project configuration. # # ------------------------------------------------------------------------------ # Specific scripts using this configuration may support interpolating values # from other options inside the same section using the '%(option)s' syntax. The # same syntax may also be used to interpolate additional values provided by the # calling script instead of being read from the configuration file itself, e.g. # value '%(__name__)s' always refers to the name of the current section. # # For example, if interpolation is enabled, configuration # # [demo-section] # basic = Foo # derived = %(basic value)s-Bar # # generates the following options located in the section 'demo-section': # # 'basic' with the value 'Foo' # 'derived' with the value 'Foo-Bar' # -------------------------------------- # --- Test & development environment --- # -------------------------------------- # Target Python environments. [env:2.4.4 x86] command = py244.cmd [env:2.4.3 x86] command = py243.cmd [env:2.5.4 x64] command = py254.cmd [env:2.5.4 x86] command = py254_x86.cmd [env:2.6.6 x64] command = py266.cmd [env:2.6.6 x86] command = py266_x86.cmd [env:2.7.6 x64] command = py276.cmd [env:2.7.6 x86] command = py276_x86.cmd [env:2.7.7 x64] command = py277.cmd [env:2.7.7 x86] command = py277_x86.cmd [env:2.7.8 x64] command = py278.cmd [env:2.7.8 x86] command = py278_x86.cmd [env:3.1.3 x64] command = py313.cmd [env:3.1.3 x86] command = py313_x86.cmd [env:3.2.5 x64] command = py325.cmd [env:3.2.5 x86] command = py325_x86.cmd [env:3.3.3 x64] command = py333.cmd [env:3.3.3 x86] command = py333_x86.cmd [env:3.3.5 x64] command = py335.cmd [env:3.3.5 x86] command = py335_x86.cmd [env:3.4.0 x64] command = py340.cmd [env:3.4.0 x86] command = py340_x86.cmd [env:3.4.1 x64] command = py341.cmd [env:3.4.1 x86] command = py341_x86.cmd [env:3.4.2 x64] command = py342.cmd [env:3.4.2 x86] command = py342_x86.cmd [tool:pytest] # Folders 'pytest' unit testing framework should avoid when collecting test # cases to run, e.g. internal build & version control system folders. norecursedirs = .git .hg .svn build dist # Regular test modules have names matching 'test_*.py'. Modules whose names end # with '__pytest_assert_rewrite_needed.py' contain testing utility # implementations that need to be considered as test modules so pytest would # apply its assertion rewriting on them or else they would not work correctly # when tests are run with disabled Python assertions. python_files = test_*.py *__pytest_assert_rewrite_needed.py [setup base environments - actions] # Regular actions (True/False). report environment configuration = False report raw environment scan results = False # Setup actions (Yes/No/IfNeeded). setup setuptools = IfNeeded download installations = IfNeeded install environments = Yes [setup base environments - folders] # Regular relative paths are interpreted relative to the folder containing this # configuration. An alternative base folder may be explicitly specified using # the following syntax: # // # Where stands for the specified relative path, while # stands for one of the following literals # PROJECT-FOLDER - base project folder # SCRIPT-FOLDER - folder containing the script reading this configuration # INI-FOLDER - folder containing this configuration (default) installation cache = SCRIPT-FOLDER//__down load__ pip download cache = %(installation cache)s/__pip download cache__ ez_setup folder = PROJECT-FOLDER//. [setup base environments - reuse pre-installed setuptools] # Reusing pre-installed setuptools distributions (True/False). old = False best = True future = True suds-1.1.2/setup.py000066400000000000000000000766111425611400200142110ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Main installation and project management script for this project. Attempts to use setuptools if available, and even attempts to install it automatically if it is not, downloading it from PyPI if needed. However, its main functionality will function just fine without setuptools as well. Having setuptools available provides us with the following benefits: - setup.py 'egg_info' command constructing the project's metadata - setup.py 'develop' command deploying the project in 'development mode', thus making it available on sys.path, yet still editable directly in its source checkout - equivalent to running a pip based installation using 'easy_install -e' or 'pip install -e' - setup.py 'test' command as a standard way to run the project's test suite - when running the installation directly from the source tree setup.py 'install' command: - automatically installs the project's metadata so package management tools like pip recognize the installation correctly, e.g. this allows using 'pip uninstall' to undo the installation - package installed as a zipped egg by default """ import sys if sys.version_info < (2, 4): print("ERROR: Python 2.4+ required") sys.exit(-2) if (3,) <= sys.version_info < (3, 1): print("ERROR: Python 3.0 not supported - please use Python 3.1+ instead") sys.exit(-2) import os import os.path import re # Workaround for a Python issue detected with Python 3.1.3 when running our # pytest based 'setup.py test' command. At the end of the test run, Python # would report an error: # # > Error in atexit._run_exitfuncs: # > TypeError: print_exception(): Exception expected for value, str found # # Workaround found suggested by Richard Oudkerk in Python's issue tracker at: # http://bugs.python.org/issue15881#msg170215 # # The error is caused by two chained Python bugs: # 1. The multiprocessing module seems to call its global cleanup function only # after all of its globals have already been released, thus raising an # exception. # 2. atexit exception handling implementation is not prepared to handle and # correctly report the exception as raised by the multiprocessing module # cleanup. if (3,) <= sys.version_info < (3, 2): import multiprocessing del multiprocessing # ----------------------------------------------------------------------------- # Global variables. # ----------------------------------------------------------------------------- distutils_cmdclass = {} extra_setup_params = {} # Hardcoded configuration. attempt_to_use_setuptools = True attempt_to_install_setuptools = True # ----------------------------------------------------------------------------- # Detect the setup.py environment - current & script folder. # ----------------------------------------------------------------------------- # Setup documentation incorrectly states that it will search for packages # relative to the setup script folder by default when in fact it will search # for them relative to the current working folder. It seems avoiding this # problem cleanly and making the setup script runnable with any current working # folder would require better distutils and/or setuptools support. # Attempted alternatives: # * Changing the current working folder internally makes any passed path # parameters be interpreted relative to the setup script folder when they # should be interpreted relative to the initial current working folder. # * Passing the script folder to setup() using the parameter # package_dir={"": script_folder} # makes the setup 'build' command work from any folder but 'egg-info' # (together with any related commands) still fails. script_folder = os.path.realpath(os.path.dirname(__file__)) current_folder = os.path.realpath(os.getcwd()) if script_folder != current_folder: print("ERROR: Suds library setup script needs to be run from the folder " "containing it.") print("") print("Current folder: %s" % current_folder) print("Script folder: %s" % script_folder) sys.exit(-2) # ----------------------------------------------------------------------------- # Import suds_devel module shared between setup & development scripts. # ----------------------------------------------------------------------------- tools_folder = os.path.join(script_folder, "tools") sys.path.insert(0, tools_folder) import suds_devel sys.path.pop(0) from suds_devel.requirements import (check_Python24_pytest_requirements, pytest_requirements, six_requirements) # ----------------------------------------------------------------------------- # Attempt to use setuptools for this installation. # ----------------------------------------------------------------------------- # setuptools brings us several useful features (see the main setup script # docstring) but if it causes problems for our users or even only # inconveniences them, they tend to complain about why we are using setuptools # at all. Therefore we try to use setuptools as silently as possible, with a # clear error message displayed to the user, but only in cases when we # absolutely need setuptools. # # Setuptools usage logic: # 1. attempt to use a preinstalled setuptools version # 2. if a preinstalled setuptools version is not available, attempt to install # and use the most recent tested compatible setuptools release, possibly # downloading the installation from PyPI into the current folder # 3. if we still do not have setuptools available, fall back to using distutils # # Note that we have made a slight trade-off here and chose to reuse an existing # setuptools installation if available instead of always downloading and # installing the most recent compatible setuptools release. # # Alternative designs and rationale for selecting the current design: # # distutils/setuptools usage: # * always use distutils # - misses out on all the setuptools features # * use setuptools if available, or fall back to using distutils # - chosen design # - gets us setuptools features if available, with clear end-user error # messages in case an operation is triggered that absolutely requires # setuptools to be available # - see below for notes on different setuptools installation alternatives # * always use setuptools # - see below for notes on different setuptools installation alternatives # # setuptools installation: # * expect setuptools to be preinstalled # - if not available, burden the user with installing setuptools manually # - see below for notes on using different setuptools versions # * use preinstalled setuptools if possible, or fall back to installing it # on-demand # - chosen design # - see below for notes on using different setuptools versions # - automated setuptools installations, and especially in-place upgrades, # can fail for various reasons (see below) # - reduces the risk of a stalled download stalling the whole setup # operation, e.g. because of an unavailable or unresponsive DNS server # * always install setuptools # - automated setuptools installations, and especially in-place upgrades, # can fail for various reasons (see below) # - user has no way to avoid setuptools installation issues by installing # setuptools himself, which would force us to make our setuptools # installation support universally applicable and that is just not # possible, e.g. users might be wanting to use customized package # management or their own package index instead of PyPI. # # setuptools version usage: # - basic problem here is that our project can be and has been tested only # with certain setuptools versions # - using a different version may have issues causing our setup procedures to # fail outside our control, either due to a setuptools bug or due to an # incompatibility between our implementation and the used setuptools # version # - some setuptools releases have known regressions, e.g. setuptools 3.0 # which is documented to fail on Python 2.6 due to an accidental backward # incompatibility corrected in the setuptools 3.1 release # * allow using any setuptools version # - chosen design # - problems caused by incompatible setuptools version usage are # considered highly unlikely and can therefore be patched up as needed # when and if they appear # - users will most likely not have a setuptools version preinstalled # into their Python environment that is incompatible with that # environment # - if there is no setuptools version installed, our setup will attempt # to install the most recent tested setuptools release # * allow using only tested setuptools versions or possibly more recent ones # - unless we can automatically install a suitable setuptools version, we # will need to burden the user with installing it manually or fall back # to using distutils # - automated setuptools installations, and especially in-place upgrades, # can fail for various reasons (see below) # - extra implementation effort required compared to the chosen design, # with no significant benefit # # Some known scenarios causing automated setuptools installation failures: # * Download failures, e.g. because user has no access to PyPI. # * In-place setuptool upgrades can fail due to a known setuptools issue (see # 'https://bitbucket.org/pypa/setuptools/issue/168') when both the original # and the new setuptools version is installed as a zipped egg distribution. # Last seen using setuptools 3.4.4. # * If the Python environment running our setup has already loaded setuptools # packages, then upgrading that installation in-place will fail with an # error message instructing the user to do the upgrade manually. # * When installing our project using pip, pip will load setuptools # internally, and it typically uses an older setuptools version which can # trigger the in-place upgrade failure as described above. What is worse, # we ignore this failure, we run into the following combination problem: # * pip calls our setup twice - once to collect the package requirement # information and once to perform the actual installation, and we do # not want to display multiple potentially complex error messages to # user for what is effectively the same error. # * Since we can not affect how external installers call our setup, to # avoid this we would need to either: # * Somehow cache the information that we already attempted and # failed to upgrade setuptools (complicated + possibly not robust). # * Patch the setuptools installation script to not display those # error messages (we would prefer to not be forced to maintain our # own patches for this script and use it as is). # * Avoid the issue by never upgrading an existing setuptools # installation (chosen design). def acquire_setuptools_setup(): if not attempt_to_use_setuptools: return def import_setuptools_setup(): try: from setuptools import setup except ImportError: return return setup setup = import_setuptools_setup() if setup or not attempt_to_install_setuptools: return setup if (3,) <= sys.version_info[:2] < (3, 2): # Setuptools contains a test module with an explicitly specified UTF-8 # BOM, which is not supported by Python's py2to3 tool prior to Python # 3.2 (see Python issue #7313). Setuptools can still be installed # manually using its ez_setup.py installer script (it will report and # ignore the error), but if we use the ez_setup.use_setuptools() # programmatic setup invocation from here - it will fail. # # There are several things that could be done here - patches welcome if # anyone actually needs them: # - the issue could be worked around by running the setuptools # installation as a separate process in this case # - warning display could be more cleanly integrated into distutils # command execution process so the warning does not get displayed # if setuptools would not actually be useful, e.g. if user just ran # our setup script with no command or with the --help option print("---") print("WARNING: can not install setuptools automatically using Python " "3.0 & 3.1") print("WARNING: if needed, install setuptools manually before running " "this installation using Python prior to version 3.2") print("---") return import suds_devel.ez_setup_versioned ez_setup = suds_devel.ez_setup_versioned.import_module() try: # Since we know there is no setuptools package in the current # environment, this will: # 1. download a setuptools source distribution to the current folder # 2. prepare an installable setuptools egg distribution in the current # folder # 3. schedule for the prepared setuptools distribution to be installed # together with our package (if our package is getting installed at # all and setup has not been called for some other purpose, e.g. # displaying its help information or running a non-install related # setup command) ez_setup.use_setuptools() except (KeyboardInterrupt, SystemExit): raise except Exception: return return import_setuptools_setup() setup = acquire_setuptools_setup() using_setuptools = bool(setup) if not using_setuptools: # Fall back to using distutils. from distutils.core import setup # ----------------------------------------------------------------------------- # Support functions. # ----------------------------------------------------------------------------- def read_python_code(filename): "Returns the given Python source file's compiled content." file = open(filename, "rt") try: source = file.read() finally: file.close() # Python 2.6 and below did not support passing strings to exec() & # compile() functions containing line separators other than '\n'. To # support them we need to manually make sure such line endings get # converted even on platforms where this is not handled by native text file # read operations. source = source.replace("\r\n", "\n").replace("\r", "\n") return compile(source, filename, "exec") def recursive_package_list(*packages): """ Returns a list of all the given packages and all their subpackages. Given packages are expected to be found relative to this script's location. Subpackages are detected by scanning the given packages' subfolder hierarchy for any folders containing the '__init__.py' module. As a consequence, namespace packages are not supported. This is our own specialized setuptools.find_packages() replacement so we can avoid the setuptools dependency. """ result = set() todo = [] for package in packages: folder = os.path.join(script_folder, *package.split(".")) if not os.path.isdir(folder): raise Exception("Folder not found for package '%s'." % (package,)) todo.append((package, folder)) while todo: package, folder = todo.pop() if package in result: continue result.add(package) for subitem in os.listdir(folder): subpackage = ".".join((package, subitem)) subfolder = os.path.join(folder, subitem) if not os.path.isfile(os.path.join(subfolder, "__init__.py")): continue todo.append((subpackage, subfolder)) return list(result) # Shamelessly stolen from setuptools project's pkg_resources module. def safe_version(version_string): """ Convert an arbitrary string to a standard version string Spaces become dots, and all other non-alphanumeric characters become dashes, with runs of multiple dashes condensed to a single dash. """ version_string = version_string.replace(" ", ".") return re.sub("[^A-Za-z0-9.]+", "-", version_string) def unicode2ascii(unicode): """Convert a unicode string to its approximate ASCII equivalent.""" return unicode.encode("ascii", 'xmlcharrefreplace').decode("ascii") # ----------------------------------------------------------------------------- # Load the suds library version information. # ----------------------------------------------------------------------------- # Load the suds library version information directly into this module without # having to import the whole suds library itself. Importing the suds package # would have caused problems like the following: # * Forcing the imported package module to be Python 3 compatible without any # lib2to3 fixers first being run on it (since such fixers get run only # later as a part of the setup procedure). # * Making the setup module depend on the package module's dependencies, thus # forcing the user to install them manually (since the setup procedure that # is supposed to install them automatically will not be able to run unless # they are already installed). # We execute explicitly compiled source code instead of having the exec() # function compile it to get a better error messages. If we used exec() on the # source code directly, the source file would have been listed as just # ''. exec(read_python_code(os.path.join(script_folder, "suds", "version.py"))) # ----------------------------------------------------------------------------- # Custom setup.py 'test' command for running the project test suite. # ----------------------------------------------------------------------------- # pytest and any of its requirements not already installed in the target Python # environment will be automatically downloaded from PyPI and installed into the # current folder as zipped egg distributions. # # Requirements: # - setup must be using setuptools # - if running Python version prior to 2.5, a suitable pytest version must # already be installed and will not be installed on demand (see the related # embedded code comment below for more detailed information) # # If the requirements are not met, the command simply reports an end-user error # message explaining why the test functionality is unavailable. # # Since Python's distutils framework does not allow passing all received # command-line arguments to its commands, it does not seem easy to customize # how pytest runs its tests this way. To have better control over this, user # should run the pytest on the target source tree directly, possibly after # first building a temporary one to work around problems like Python 2/3 # compatibility. def test_requirements(): """ Return test requirements for the 'test' command or an error string. An error is reported if the requirements can not be satisfied for some reason. Exact required packages and their versions vary depending on our target Python environment version as pytest dropped backward compatibility support for some of the Python versions we still support in this project. """ if not using_setuptools: return "test command not available without setuptools" include_pytest_requirements = True if sys.version_info < (2, 5): # pytest requirements can not be installed automatically by this setup # script under Python 2.4.x environment. Specific pytest & py library # package version combination that we found working in Python 2.4.x # environments does not formally satisfy pytest requirements, and we # found no way to make setuptools' test command succeed when this # script installs packages that do not have all their formal # requirements satisfied. have_pytest, have_py = check_Python24_pytest_requirements() if not have_pytest: return "compatible preinstalled pytest needed prior to Python 2.5" if not have_py: return "compatible preinstalled py needed prior to Python 2.5" # We must not explicitly specify pytest requirements when running the # tests using a Python 2.4.x environment as the only way we found we # can run our tests there is to use formally incompatible pytest & py # packages. Explicitly specifying pytest requirements here would then # cause setuptols to verify those requirements prior to running our # test suite. include_pytest_requirements = False if ((3,) <= sys.version_info < (3, 2, 3)): # Python 3.x versions prior to Python 3.2.3 have a bug in their inspect # module causing inspect.getmodule() calls to fail if some module lazy # loads other modules when some of its attributes are accessed. For # more detailed information see Python development issue #13487 # (http://bugs.python.org/issue13487). # # This occurs when using setuptools to install our project into a # Python 3.1 environment. There the py.error module seems to do such # lazy loading. Forcing that module to be loaded here, before the # setuptools installation procedure, avoids the issue. try: import py.error py.error.__attribute_access_to_force_this_module_to_lazy_load__ except (AttributeError, ImportError): pass # When using Python 2.5 on Windows, if setuptools chooses to install the # colorama package (pytest requirement on Windows) older than version # 0.1.11, running our 'setup.py test' command may show benign error # messages caused by some colorama atexit handlers getting triggered # multiple times. There are no adverse effects to this and the issue only # occurs if the package is not already installed and it is the 'setup.py # test' command that is installing it. The issue has been fixed by the next # Python 2.5 compatible colorama 0.3.2 release. result = [] if include_pytest_requirements: result.extend(pytest_requirements()) result.extend(six_requirements()) return result test_error = None tests_require = test_requirements() if isinstance(tests_require, str): test_error = tests_require else: extra_setup_params["tests_require"] = tests_require if test_error: import distutils.cmd import distutils.errors class TestCommand(distutils.cmd.Command): description = test_error user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): raise distutils.errors.DistutilsPlatformError(self.description) else: from setuptools.command.test import (normalize_path as _normalize_path, test as _test) class TestCommand(_test): # Derived from setuptools.command.test.test for its # with_project_on_sys_path() method. # The test build can not be done in-place with Python 3+ as it requires # py2to3 conversion which we do not want modifying our original project # sources. if sys.version_info < (3,): description = "run pytest based unit tests after an in-place build" else: description = "run pytest based unit tests after a build" # Override base class's command-line options. #TODO: pytest argument passing support could be improved if we could # get distutils/setuptools to pass all unrecognized command-line # parameters to us instead of complaining about them. user_options = [("pytest-args=", "a", "arguments to pass to pytest " "(whitespace separated, whitespaces in arguments not supported)")] def initialize_options(self): self.pytest_args = None def finalize_options(self): self.test_args = [] if self.pytest_args is not None: self.test_args = self.pytest_args.split() def run(self): # shamelessly lifted from setuptools.command.test.test.run() dist = self.distribution if dist.install_requires: dist.fetch_build_eggs(dist.install_requires) if dist.tests_require: dist.fetch_build_eggs(dist.tests_require) cmd = self._test_cmd_string() if self.dry_run: self.announce("skipping '%s' (dry run)" % (cmd,)) else: self.announce("running '%s'" % (cmd,)) self.with_project_on_sys_path(self.run_tests) def run_tests(self): import pytest sys.exit(pytest.main(self.test_args)) def _test_cmd_string(self): parts = ["pytest"] if self.pytest_args: parts.append(self.pytest_args) return " ".join(parts) distutils_cmdclass["test"] = TestCommand # ----------------------------------------------------------------------------- # Mark the original suds project as obsolete. # ----------------------------------------------------------------------------- if sys.version_info >= (2, 5): # distutils.setup() 'obsoletes' parameter not introduced until Python 2.5. extra_setup_params["obsoletes"] = ["suds"] # ----------------------------------------------------------------------------- # Avoid setup warnings when constructing a list of all project sources. # ----------------------------------------------------------------------------- # Part of this workaround implemented and part in the project's MANIFEST.in # template. See a related comment in MANIFEST.in for more detailed information. dummy_tools_folder = os.path.join(tools_folder, "__dummy__") dummy_tools_file = os.path.join(dummy_tools_folder, "readme.txt") try: if not os.path.isdir(dummy_tools_folder): os.mkdir(dummy_tools_folder) if not os.path.isfile(dummy_tools_file): f = open(dummy_tools_file, "w") try: f.write("""\ Dummy empty folder added as a part of a patch to silence setup.py warnings when determining which files belong to the project. See a related comment in the project's MANIFEST.in template for more detailed information. Both the folder and this file have been generated by the project's setup.py script and should not be placed under version control. """) finally: f.close() except EnvironmentError: # Something went wrong attempting to construct the dummy file. Ah well, we # gave it our best. Continue on with possible spurious warnings. pass # ----------------------------------------------------------------------------- # Set up project metadata and run the actual setup. # ----------------------------------------------------------------------------- # Package meta-data needs may be specified as: # * Python 2.x - UTF-8 encoded bytes # * Python [2.6, 3.0> - unicode string # - unicode strings containing non-ASCII characters supported since Python # commit 4c683ec4415b3c4bfbc7fe7a836b949cb7beea03 # * Python [3.0, 3.2.2> # - may only contain ASCII characters due to a distutils bug (given input # can only be a unicode string and is encoded using the user's default # system code-page, e.g. typically CP1250 on eastern European Windows, # CP1252 on western European Windows, UTF-8 on Linux or any other) # - setuptools 3.5 works around the issue by overriding relevant distutils # functionality, allowing the use of non-ASCII characters, but only for # Python 3.1 # * Python 3.2.2+ - unicode string # - unicode strings containing non-ASCII characters supported since Python # commit fb4d2e6d393e96baac13c4efc216e361bf12c293 can_not_use_non_ASCII_meta_data = (3,) <= sys.version_info < (3, 2, 2) if (can_not_use_non_ASCII_meta_data and using_setuptools and sys.version_info[:2] == (3, 1)): from setuptools import __version__ as setuptools_version from pkg_resources import parse_version as pv can_not_use_non_ASCII_meta_data = pv(setuptools_version) < pv("3.5") # Wrap long_description at 72 characters since the PKG-INFO package # distribution metadata file stores this text with an 8 space indentation. long_description = """ --------------------------------------- Lightweight SOAP client (Community fork). --------------------------------------- Based on the original 'suds' project by Jeff Ortel (jortel at redhat dot com) hosted at 'http://fedorahosted.org/suds'. 'Suds' is a lightweight SOAP-based web service client for Python licensed under LGPL (see the LICENSE.txt file included in the distribution). """ package_name = os.environ.get('SUDS_PACKAGE', 'suds-community') version_tag = safe_version(__version__) project_url = "https://github.com/suds-community/suds" base_download_url = project_url + "/archive" download_distribution_name = "release-%s.tar.gz" % (version_tag) download_url = "%s/%s" % (base_download_url, download_distribution_name) maintainer="Jurko Gospodnetić" if can_not_use_non_ASCII_meta_data: maintainer = unicode2ascii(maintainer) setup( name=package_name, version=__version__, description="Lightweight SOAP client (community fork)", long_description=long_description, long_description_content_type='text/markdown', keywords=["SOAP", "web", "service", "client"], url=project_url, download_url=download_url, packages=recursive_package_list("suds"), author="Jeff Ortel", author_email="jortel@redhat.com", maintainer=maintainer, maintainer_email="jurko.gospodnetic@pke.hr", # See PEP-301 for the classifier specification. For a complete list of # available classifiers see # 'http://pypi.python.org/pypi?%3Aaction=list_classifiers'. classifiers=["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: " "GNU Library or Lesser General Public License (LGPL)", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Topic :: Internet"], # PEP-314 states that, if possible, license & platform should be specified # using 'classifiers'. license="(specified using classifiers)", platforms=["(specified using classifiers)"], # Register distutils command customizations. cmdclass=distutils_cmdclass, python_requires=">=3.5", **extra_setup_params) suds-1.1.2/suds/000077500000000000000000000000001425611400200134425ustar00rootroot00000000000000suds-1.1.2/suds/__init__.py000066400000000000000000000113141425611400200155530ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Lightweight SOAP Python client providing a Web Service proxy. """ import sys # # Project properties # from .version import __build__, __version__ # # Exceptions # class MethodNotFound(Exception): def __init__(self, name): Exception.__init__(self, "Method not found: '%s'" % (name,)) class PortNotFound(Exception): def __init__(self, name): Exception.__init__(self, "Port not found: '%s'" % (name,)) class ServiceNotFound(Exception): def __init__(self, name): Exception.__init__(self, "Service not found: '%s'" % (name,)) class TypeNotFound(Exception): def __init__(self, name): Exception.__init__(self, "Type not found: '%s'" % (tostr(name),)) class BuildError(Exception): def __init__(self, name, exception): Exception.__init__(self, "An error occurred while building an " "instance of (%s). As a result the object you requested could not " "be constructed. It is recommended that you construct the type " "manually using a Suds object. Please open a ticket with a " "description of this error. Reason: %s" % (name, exception)) class WebFault(Exception): def __init__(self, fault, document): if hasattr(fault, "faultstring"): Exception.__init__(self, "Server raised fault: '%s'" % (fault.faultstring,)) self.fault = fault self.document = document # # Logging # class Repr: def __init__(self, x): self.x = x def __str__(self): return repr(self.x) # # Utility # class null: """I{null} object used to pass NULL for optional XML nodes.""" pass def objid(obj): return obj.__class__.__name__ + ":" + hex(id(obj)) def tostr(object, encoding=None): """Get a unicode safe string representation of an object.""" if isinstance(object, str): if encoding is None: return object return object.encode(encoding) if isinstance(object, tuple): s = ["("] for item in object: s.append(tostr(item)) s.append(", ") s.append(")") return "".join(s) if isinstance(object, list): s = ["["] for item in object: s.append(tostr(item)) s.append(", ") s.append("]") return "".join(s) if isinstance(object, dict): s = ["{"] for item in list(object.items()): s.append(tostr(item[0])) s.append(" = ") s.append(tostr(item[1])) s.append(", ") s.append("}") return "".join(s) try: return str(object) except Exception: return str(object) # # Python 3 compatibility # if sys.version_info < (3, 0): from io import StringIO as BytesIO else: from io import BytesIO # Idea from 'http://lucumr.pocoo.org/2011/1/22/forwards-compatible-python'. class UnicodeMixin(object): if sys.version_info >= (3, 0): # For Python 3, __str__() and __unicode__() should be identical. __str__ = lambda x: x.__unicode__() else: __str__ = lambda x: str(x).encode("utf-8") # Used instead of byte literals as they are not supported on Python versions # prior to 2.6. def byte_str(s="", encoding="utf-8", input_encoding="utf-8", errors="strict"): """ Returns a byte string version of 's', encoded as specified in 'encoding'. Accepts str & unicode objects, interpreting non-unicode strings as byte strings encoded using the given input encoding. """ assert isinstance(s, str) if isinstance(s, str): return s.encode(encoding, errors) if s and encoding != input_encoding: return s.decode(input_encoding, errors).encode(encoding, errors) return s # Class used to represent a byte string. Useful for asserting that correct # string types are being passed around where needed. if sys.version_info >= (3, 0): byte_str_class = bytes else: byte_str_class = str suds-1.1.2/suds/argparser.py000066400000000000000000000365231425611400200160130ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Suds web service operation invocation function argument parser. See the parse_args() function description for more detailed information. """ __all__ = ["parse_args"] def parse_args(method_name, param_defs, args, kwargs, external_param_processor, extra_parameter_errors): """ Parse arguments for suds web service operation invocation functions. Suds prepares Python function objects for invoking web service operations. This function implements generic binding agnostic part of processing the arguments passed when calling those function objects. Argument parsing rules: * Each input parameter element should be represented by single regular Python function argument. * At most one input parameter belonging to a single choice parameter structure may have its value specified as something other than None. * Positional arguments are mapped to choice group input parameters the same as is done for a simple all/sequence group - each in turn. Expects to be passed the web service operation's parameter definitions (parameter name, type & optional ancestry information) in order and, based on that, extracts the values for those parameter from the arguments provided in the web service operation invocation call. Ancestry information describes parameters constructed based on suds library's automatic input parameter structure unwrapping. It is expected to include the parameter's XSD schema 'ancestry' context, i.e. a list of all the parent XSD schema tags containing the parameter's tag. Such ancestry context provides detailed information about how the parameter's value is expected to be used, especially in relation to other input parameters, e.g. at most one parameter value may be specified for parameters directly belonging to the same choice input group. Rules on acceptable ancestry items: * Ancestry item's choice() method must return whether the item represents a XSD schema tag. * Passed ancestry items are used 'by address' internally and the same XSD schema tag is expected to be identified by the exact same ancestry item object during the whole argument processing. During processing, each parameter's definition and value, together with any additional pertinent information collected from the encountered parameter definition structure, is passed on to the provided external parameter processor function. There that information is expected to be used to construct the actual binding specific web service operation invocation request. Raises a TypeError exception in case any argument related errors are detected. The exceptions raised have been constructed to make them as similar as possible to their respective exceptions raised during regular Python function argument checking. Does not support multiple same-named input parameters. """ arg_parser = _ArgParser(method_name, param_defs, external_param_processor) return arg_parser(args, kwargs, extra_parameter_errors) class _ArgParser: """Internal argument parser implementation function object.""" def __init__(self, method_name, param_defs, external_param_processor): self.__method_name = method_name self.__param_defs = param_defs self.__external_param_processor = external_param_processor self.__stack = [] def __call__(self, args, kwargs, extra_parameter_errors): """ Runs the main argument parsing operation. Passed args & kwargs objects are not modified during parsing. Returns an informative 2-tuple containing the number of required & allowed arguments. """ assert not self.active(), "recursive argument parsing not allowed" self.__init_run(args, kwargs, extra_parameter_errors) try: self.__process_parameters() return self.__all_parameters_processed() finally: self.__cleanup_run() assert not self.active() def active(self): """ Return whether this object is currently running argument processing. Used to avoid recursively entering argument processing from within an external parameter processor. """ return bool(self.__stack) def __all_parameters_processed(self): """ Finish the argument processing. Should be called after all the web service operation's parameters have been successfully processed and, afterwards, no further parameter processing is allowed. Returns a 2-tuple containing the number of required & allowed arguments. See the _ArgParser class description for more detailed information. """ assert self.active() sentinel_frame = self.__stack[0] self.__pop_frames_above(sentinel_frame) assert len(self.__stack) == 1 self.__pop_top_frame() assert not self.active() args_required = sentinel_frame.args_required() args_allowed = sentinel_frame.args_allowed() self.__check_for_extra_arguments(args_required, args_allowed) return args_required, args_allowed def __check_for_extra_arguments(self, args_required, args_allowed): """ Report an error in case any extra arguments are detected. Does nothing if reporting extra arguments as exceptions has not been enabled. May only be called after the argument processing has been completed. """ assert not self.active() if not self.__extra_parameter_errors: return if self.__kwargs: param_name = list(self.__kwargs.keys())[0] if param_name in self.__params_with_arguments: msg = "got multiple values for parameter '%s'" else: msg = "got an unexpected keyword argument '%s'" self.__error(msg % (param_name,)) if self.__args: def plural_suffix(count): if count == 1: return "" return "s" def plural_was_were(count): if count == 1: return "was" return "were" expected = args_required if args_required != args_allowed: expected = "%d to %d" % (args_required, args_allowed) given = self.__args_count msg_parts = ["takes %s positional argument" % (expected,), plural_suffix(expected), " but %d " % (given,), plural_was_were(given), " given"] self.__error("".join(msg_parts)) def __cleanup_run(self): """Cleans up after a completed argument parsing run.""" self.__stack = [] assert not self.active() def __error(self, message): """Report an argument processing error.""" raise TypeError("%s() %s" % (self.__method_name, message)) def __frame_factory(self, ancestry_item): """Construct a new frame representing the given ancestry item.""" frame_class = Frame if ancestry_item is not None and ancestry_item.choice(): frame_class = ChoiceFrame return frame_class(ancestry_item, self.__error, self.__extra_parameter_errors) def __get_param_value(self, name): """ Extract a parameter value from the remaining given arguments. Returns a 2-tuple consisting of the following: * Boolean indicating whether an argument has been specified for the requested input parameter. * Parameter value. """ if self.__args: return True, self.__args.pop(0) try: value = self.__kwargs.pop(name) except KeyError: return False, None return True, value def __in_choice_context(self): """ Whether we are currently processing a choice parameter group. This includes processing a parameter defined directly or indirectly within such a group. May only be called during parameter processing or the result will be calculated based on the context left behind by the previous parameter processing if any. """ for x in self.__stack: if x.__class__ is ChoiceFrame: return True return False def __init_run(self, args, kwargs, extra_parameter_errors): """Initializes data for a new argument parsing run.""" assert not self.active() self.__args = list(args) self.__kwargs = dict(kwargs) self.__extra_parameter_errors = extra_parameter_errors self.__args_count = len(args) + len(kwargs) self.__params_with_arguments = set() self.__stack = [] self.__push_frame(None) def __match_ancestry(self, ancestry): """ Find frames matching the given ancestry. Returns a tuple containing the following: * Topmost frame matching the given ancestry or the bottom-most sentry frame if no frame matches. * Unmatched ancestry part. """ stack = self.__stack if len(stack) == 1: return stack[0], ancestry previous = stack[0] for frame, n in zip(stack[1:], range(len(ancestry))): if frame.id() is not ancestry[n]: return previous, ancestry[n:] previous = frame return frame, ancestry[n + 1:] def __pop_frames_above(self, frame): """Pops all the frames above, but not including the given frame.""" while self.__stack[-1] is not frame: self.__pop_top_frame() assert self.__stack def __pop_top_frame(self): """Pops the top frame off the frame stack.""" popped = self.__stack.pop() if self.__stack: self.__stack[-1].process_subframe(popped) def __process_parameter(self, param_name, param_type, ancestry=None): """Collect values for a given web service operation input parameter.""" assert self.active() param_optional = param_type.optional() has_argument, value = self.__get_param_value(param_name) if has_argument: self.__params_with_arguments.add(param_name) self.__update_context(ancestry) self.__stack[-1].process_parameter(param_optional, value is not None) self.__external_param_processor(param_name, param_type, self.__in_choice_context(), value) def __process_parameters(self): """Collect values for given web service operation input parameters.""" for pdef in self.__param_defs: self.__process_parameter(*pdef) def __push_frame(self, ancestry_item): """Push a new frame on top of the frame stack.""" frame = self.__frame_factory(ancestry_item) self.__stack.append(frame) def __push_frames(self, ancestry): """ Push new frames representing given ancestry items. May only be given ancestry items other than None. Ancestry item None represents the internal sentinel item and should never appear in a given parameter's ancestry information. """ for x in ancestry: assert x is not None self.__push_frame(x) def __update_context(self, ancestry): if not ancestry: return match_result = self.__match_ancestry(ancestry) last_matching_frame, unmatched_ancestry = match_result self.__pop_frames_above(last_matching_frame) self.__push_frames(unmatched_ancestry) class Frame: """ Base _ArgParser context frame. When used directly, as opposed to using a derived class, may represent any input parameter context/ancestry item except a choice order indicator. """ def __init__(self, id, error, extra_parameter_errors): """ Construct a new Frame instance. Passed error function is used to report any argument checking errors. """ assert self.__class__ != Frame or not id or not id.choice() self.__id = id self._error = error self._extra_parameter_errors = extra_parameter_errors self._args_allowed = 0 self._args_required = 0 self._has_value = False def args_allowed(self): return self._args_allowed def args_required(self): return self._args_required def has_value(self): return self._has_value def id(self): return self.__id def process_parameter(self, optional, has_value): args_required = 1 if optional: args_required = 0 self._process_item(has_value, 1, args_required) def process_subframe(self, subframe): self._process_item( subframe.has_value(), subframe.args_allowed(), subframe.args_required()) def _process_item(self, has_value, args_allowed, args_required): self._args_allowed += args_allowed self._args_required += args_required if has_value: self._has_value = True class ChoiceFrame(Frame): """ _ArgParser context frame representing a choice order indicator. A choice requires as many input arguments as are needed to satisfy the least requiring of its items. For example, if we use I(n) to identify an item requiring n parameter, then a choice containing I(2), I(3) & I(7) requires 2 arguments while a choice containing I(5) & I(4) requires 4. Accepts an argument for each of its contained elements but allows at most one of its directly contained items to have a defined value. """ def __init__(self, id, error, extra_parameter_errors): assert id.choice() Frame.__init__(self, id, error, extra_parameter_errors) self.__has_item = False def _process_item(self, has_value, args_allowed, args_required): self._args_allowed += args_allowed self.__update_args_required_for_item(args_required) self.__update_has_value_for_item(has_value) def __update_args_required_for_item(self, item_args_required): if not self.__has_item: self.__has_item = True self._args_required = item_args_required return self._args_required = min(self.args_required(), item_args_required) def __update_has_value_for_item(self, item_has_value): if item_has_value: if self.has_value() and self._extra_parameter_errors: self._error("got multiple values for a single choice " "parameter") self._has_value = True suds-1.1.2/suds/bindings/000077500000000000000000000000001425611400200152375ustar00rootroot00000000000000suds-1.1.2/suds/bindings/__init__.py000066400000000000000000000016221425611400200173510ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Provides modules containing classes to support Web Services (SOAP) bindings. """ suds-1.1.2/suds/bindings/binding.py000066400000000000000000000400431425611400200172240ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ (WS) SOAP binding classes. """ from suds import * from suds.sax import Namespace from suds.sax.document import Document from suds.sax.element import Element from suds.sudsobject import Factory from suds.mx import Content from suds.mx.literal import Literal as MxLiteral from suds.umx.typed import Typed as UmxTyped from suds.bindings.multiref import MultiRef from suds.xsd.query import TypeQuery, ElementQuery from suds.xsd.sxbasic import Element as SchemaElement from suds.options import Options from suds.plugin import PluginContainer from copy import deepcopy envns = ("SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/") class Binding(object): """ The SOAP binding class used to process outgoing and incoming SOAP messages per the WSDL port binding. @ivar wsdl: The WSDL. @type wsdl: L{suds.wsdl.Definitions} @ivar schema: The collective schema contained within the WSDL. @type schema: L{xsd.schema.Schema} @ivar options: A dictionary options. @type options: L{Options} """ def __init__(self, wsdl): """ @param wsdl: A WSDL. @type wsdl: L{wsdl.Definitions} """ self.wsdl = wsdl self.multiref = MultiRef() def schema(self): return self.wsdl.schema def options(self): return self.wsdl.options def unmarshaller(self): """ Get the appropriate schema based XML decoder. @return: Typed unmarshaller. @rtype: L{UmxTyped} """ return UmxTyped(self.schema()) def marshaller(self): """ Get the appropriate XML encoder. @return: An L{MxLiteral} marshaller. @rtype: L{MxLiteral} """ return MxLiteral(self.schema(), self.options().xstq) def param_defs(self, method): """ Get parameter definitions. Each I{pdef} is a (I{name}, L{xsd.sxbase.SchemaObject}) tuple. @param method: A service method. @type method: I{service.Method} @return: A collection of parameter definitions @rtype: [I{pdef},...] """ raise Exception("not implemented") def get_message(self, method, args, kwargs): """ Get a SOAP message for the specified method, args and SOAP headers. This is the entry point for creating an outbound SOAP message. @param method: The method being invoked. @type method: I{service.Method} @param args: A list of args for the method invoked. @type args: list @param kwargs: Named (keyword) args for the method invoked. @type kwargs: dict @return: The SOAP envelope. @rtype: L{Document} """ content = self.headercontent(method) header = self.header(content) content = self.bodycontent(method, args, kwargs) body = self.body(content) env = self.envelope(header, body) if self.options().prefixes: body.normalizePrefixes() env.promotePrefixes() else: env.refitPrefixes() return Document(env) def get_reply(self, method, replyroot): """ Process the I{reply} for the specified I{method} by unmarshalling it into into Python object(s). @param method: The name of the invoked method. @type method: str @param replyroot: The reply XML root node received after invoking the specified method. @type replyroot: L{Element} @return: The unmarshalled reply. The returned value is an L{Object} or a I{list} depending on whether the service returns a single object or a collection. @rtype: L{Object} or I{list} """ soapenv = replyroot.getChild("Envelope", envns) soapenv.promotePrefixes() soapbody = soapenv.getChild("Body", envns) soapbody = self.multiref.process(soapbody) nodes = self.replycontent(method, soapbody) rtypes = self.returned_types(method) if len(rtypes) > 1: return self.replycomposite(rtypes, nodes) if len(rtypes) == 0: return if rtypes[0].multi_occurrence(): return self.replylist(rtypes[0], nodes) if len(nodes): resolved = rtypes[0].resolve(nobuiltin=True) return self.unmarshaller().process(nodes[0], resolved) def replylist(self, rt, nodes): """ Construct a I{list} reply. Called for replies with possible multiple occurrences. @param rt: The return I{type}. @type rt: L{suds.xsd.sxbase.SchemaObject} @param nodes: A collection of XML nodes. @type nodes: [L{Element},...] @return: A list of I{unmarshalled} objects. @rtype: [L{Object},...] """ resolved = rt.resolve(nobuiltin=True) unmarshaller = self.unmarshaller() return [unmarshaller.process(node, resolved) for node in nodes] def replycomposite(self, rtypes, nodes): """ Construct a I{composite} reply. Called for replies with multiple output nodes. @param rtypes: A list of known return I{types}. @type rtypes: [L{suds.xsd.sxbase.SchemaObject},...] @param nodes: A collection of XML nodes. @type nodes: [L{Element},...] @return: The I{unmarshalled} composite object. @rtype: L{Object},... """ dictionary = {} for rt in rtypes: dictionary[rt.name] = rt unmarshaller = self.unmarshaller() composite = Factory.object("reply") for node in nodes: tag = node.name rt = dictionary.get(tag) if rt is None: if node.get("id") is None and not self.options().allowUnknownMessageParts: message = "<%s/> not mapped to message part" % (tag,) raise Exception(message) continue resolved = rt.resolve(nobuiltin=True) sobject = unmarshaller.process(node, resolved) value = getattr(composite, tag, None) if value is None: if rt.multi_occurrence(): value = [] setattr(composite, tag, value) value.append(sobject) else: setattr(composite, tag, sobject) else: if not isinstance(value, list): value = [value,] setattr(composite, tag, value) value.append(sobject) return composite def mkparam(self, method, pdef, object): """ Builds a parameter for the specified I{method} using the parameter definition (pdef) and the specified value (object). @param method: A method name. @type method: str @param pdef: A parameter definition. @type pdef: tuple: (I{name}, L{xsd.sxbase.SchemaObject}) @param object: The parameter value. @type object: any @return: The parameter fragment. @rtype: L{Element} """ marshaller = self.marshaller() content = Content(tag=pdef[0], value=object, type=pdef[1], real=pdef[1].resolve()) return marshaller.process(content) def mkheader(self, method, hdef, object): """ Builds a soapheader for the specified I{method} using the header definition (hdef) and the specified value (object). @param method: A method name. @type method: str @param hdef: A header definition. @type hdef: tuple: (I{name}, L{xsd.sxbase.SchemaObject}) @param object: The header value. @type object: any @return: The parameter fragment. @rtype: L{Element} """ marshaller = self.marshaller() if isinstance(object, (list, tuple)): return [self.mkheader(method, hdef, item) for item in object] content = Content(tag=hdef[0], value=object, type=hdef[1]) return marshaller.process(content) def envelope(self, header, body): """ Build the B{} for a SOAP outbound message. @param header: The SOAP message B{header}. @type header: L{Element} @param body: The SOAP message B{body}. @type body: L{Element} @return: The SOAP envelope containing the body and header. @rtype: L{Element} """ env = Element("Envelope", ns=envns) env.addPrefix(Namespace.xsins[0], Namespace.xsins[1]) env.append(header) env.append(body) return env def header(self, content): """ Build the B{} for a SOAP outbound message. @param content: The header content. @type content: L{Element} @return: The SOAP body fragment. @rtype: L{Element} """ header = Element("Header", ns=envns) header.append(content) return header def bodycontent(self, method, args, kwargs): """ Get the content for the SOAP I{body} node. @param method: A service method. @type method: I{service.Method} @param args: method parameter values. @type args: list @param kwargs: Named (keyword) args for the method invoked. @type kwargs: dict @return: The XML content for the . @rtype: [L{Element},...] """ raise Exception("not implemented") def headercontent(self, method): """ Get the content for the SOAP I{Header} node. @param method: A service method. @type method: I{service.Method} @return: The XML content for the . @rtype: [L{Element},...] """ content = [] wsse = self.options().wsse if wsse is not None: content.append(wsse.xml()) headers = self.options().soapheaders if not isinstance(headers, (tuple, list, dict)): headers = (headers,) elif not headers: return content pts = self.headpart_types(method) if isinstance(headers, (tuple, list)): n = 0 for header in headers: if isinstance(header, Element): content.append(deepcopy(header)) continue if len(pts) == n: break h = self.mkheader(method, pts[n], header) ns = pts[n][1].namespace("ns0") h.setPrefix(ns[0], ns[1]) content.append(h) n += 1 else: for pt in pts: header = headers.get(pt[0]) if header is None: continue h = self.mkheader(method, pt, header) ns = pt[1].namespace("ns0") h.setPrefix(ns[0], ns[1]) content.append(h) return content def replycontent(self, method, body): """ Get the reply body content. @param method: A service method. @type method: I{service.Method} @param body: The SOAP body. @type body: L{Element} @return: The body content. @rtype: [L{Element},...] """ raise Exception("not implemented") def body(self, content): """ Build the B{} for a SOAP outbound message. @param content: The body content. @type content: L{Element} @return: The SOAP body fragment. @rtype: L{Element} """ body = Element("Body", ns=envns) body.append(content) return body def bodypart_types(self, method, input=True): """ Get a list of I{parameter definitions} (pdefs) defined for the specified method. An input I{pdef} is a (I{name}, L{xsd.sxbase.SchemaObject}) tuple, while an output I{pdef} is a L{xsd.sxbase.SchemaObject}. @param method: A service method. @type method: I{service.Method} @param input: Defines input/output message. @type input: boolean @return: A list of parameter definitions @rtype: [I{pdef},...] """ if input: parts = method.soap.input.body.parts else: parts = method.soap.output.body.parts return [self.__part_type(p, input) for p in parts] def headpart_types(self, method, input=True): """ Get a list of header I{parameter definitions} (pdefs) defined for the specified method. An input I{pdef} is a (I{name}, L{xsd.sxbase.SchemaObject}) tuple, while an output I{pdef} is a L{xsd.sxbase.SchemaObject}. @param method: A service method. @type method: I{service.Method} @param input: Defines input/output message. @type input: boolean @return: A list of parameter definitions @rtype: [I{pdef},...] """ if input: headers = method.soap.input.headers else: headers = method.soap.output.headers return [self.__part_type(h.part, input) for h in headers] def returned_types(self, method): """ Get the I{method} return value type(s). @param method: A service method. @type method: I{service.Method} @return: Method return value type. @rtype: [L{xsd.sxbase.SchemaObject},...] """ return self.bodypart_types(method, input=False) def __part_type(self, part, input): """ Get a I{parameter definition} (pdef) defined for a given body or header message part. An input I{pdef} is a (I{name}, L{xsd.sxbase.SchemaObject}) tuple, while an output I{pdef} is a L{xsd.sxbase.SchemaObject}. @param part: A service method input or output part. @type part: I{suds.wsdl.Part} @param input: Defines input/output message. @type input: boolean @return: A list of parameter definitions @rtype: [I{pdef},...] """ if part.element is None: query = TypeQuery(part.type) else: query = ElementQuery(part.element) part_type = query.execute(self.schema()) if part_type is None: raise TypeNotFound(query.ref) if part.type is not None: part_type = PartElement(part.name, part_type) if not input: return part_type if part_type.name is None: return part.name, part_type return part_type.name, part_type class PartElement(SchemaElement): """ Message part referencing an XSD type and thus acting like an XSD element. @ivar resolved: The part type. @type resolved: L{suds.xsd.sxbase.SchemaObject} """ def __init__(self, name, resolved): """ @param name: The part name. @type name: str @param resolved: The part type. @type resolved: L{suds.xsd.sxbase.SchemaObject} """ root = Element("element", ns=Namespace.xsdns) SchemaElement.__init__(self, resolved.schema, root) self.__resolved = resolved self.name = name self.form_qualified = False def implany(self): pass def optional(self): return True def namespace(self, prefix=None): return Namespace.default def resolve(self, nobuiltin=False): if nobuiltin and self.__resolved.builtin(): return self return self.__resolved suds-1.1.2/suds/bindings/document.py000066400000000000000000000131261425611400200174320ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Classes for the (WS) SOAP I{document/literal} binding. """ from suds import * from suds.argparser import parse_args from suds.bindings.binding import Binding from suds.sax.element import Element class Document(Binding): """ The document/literal style. Literal is the only (@use) supported since document/encoded is pretty much dead. Although the SOAP specification supports multiple documents within the SOAP , it is very uncommon. As such, suds library supports presenting an I{RPC} view of service methods defined with only a single document parameter. To support the complete specification, service methods defined with multiple documents (multiple message parts), are still presented using a full I{document} view. More detailed description: An interface is considered I{wrapped} if: - There is exactly one message part in that interface. - The message part resolves to an element of a non-builtin type. Otherwise it is considered I{bare}. I{Bare} interface is interpreted directly as specified in the WSDL schema, with each message part represented by a single parameter in the suds library web service operation proxy interface (input or output). I{Wrapped} interface is interpreted without the external wrapping document structure, with each of its contained elements passed through suds library's web service operation proxy interface (input or output) individually instead of as a single I{document} object. """ def bodycontent(self, method, args, kwargs): wrapped = method.soap.input.body.wrapped if wrapped: pts = self.bodypart_types(method) root = self.document(pts[0]) else: root = [] def add_param(param_name, param_type, in_choice_context, value): """ Construct request data for the given input parameter. Called by our argument parser for every input parameter, in order. A parameter's type is identified by its corresponding XSD schema element. """ # Do not construct request data for undefined input parameters # defined inside a choice order indicator. An empty choice # parameter can still be included in the constructed request by # explicitly providing an empty string value for it. #TODO: This functionality might be better placed inside the # mkparam() function but to do that we would first need to better # understand how different Binding subclasses in suds work and how # they would be affected by this change. if in_choice_context and value is None: return # Construct request data for the current input parameter. pdef = (param_name, param_type) p = self.mkparam(method, pdef, value) if p is None: return if not wrapped: ns = param_type.namespace("ns0") p.setPrefix(ns[0], ns[1]) root.append(p) parse_args(method.name, self.param_defs(method), args, kwargs, add_param, self.options().extraArgumentErrors) return root def replycontent(self, method, body): if method.soap.output.body.wrapped: return body[0].children return body.children def document(self, wrapper): """ Get the document root. For I{document/literal}, this is the name of the wrapper element qualified by the schema's target namespace. @param wrapper: The method name. @type wrapper: L{xsd.sxbase.SchemaObject} @return: A root element. @rtype: L{Element} """ tag = wrapper[1].name ns = wrapper[1].namespace("ns0") return Element(tag, ns=ns) def mkparam(self, method, pdef, object): """ Expand list parameters into individual parameters each with the type information. This is because in document arrays are simply multi-occurrence elements. """ if isinstance(object, (list, tuple)): return [self.mkparam(method, pdef, item) for item in object] return super(Document, self).mkparam(method, pdef, object) def param_defs(self, method): """Get parameter definitions for document literal.""" pts = self.bodypart_types(method) if not method.soap.input.body.wrapped: return pts pt = pts[0][1].resolve() return [(c.name, c, a) for c, a in pt if not c.isattr()] def returned_types(self, method): rts = super(Document, self).returned_types(method) if not method.soap.output.body.wrapped: return rts return [child for child, ancestry in rts[0].resolve(nobuiltin=True)] suds-1.1.2/suds/bindings/multiref.py000066400000000000000000000100631425611400200174400ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Provides classes for handling soap multirefs. """ from suds import * from suds.sax.element import Element soapenc = (None, 'http://schemas.xmlsoap.org/soap/encoding/') class MultiRef: """ Resolves and replaces multirefs. @ivar nodes: A list of non-multiref nodes. @type nodes: list @ivar catalog: A dictionary of multiref nodes by id. @type catalog: dict """ def __init__(self): self.nodes = [] self.catalog = {} def process(self, body): """ Process the specified soap envelope body and replace I{multiref} node references with the contents of the referenced node. @param body: A soap envelope body node. @type body: L{Element} @return: The processed I{body} @rtype: L{Element} """ self.nodes = [] self.catalog = {} self.build_catalog(body) self.update(body) body.children = self.nodes return body def update(self, node): """ Update the specified I{node} by replacing the I{multiref} references with the contents of the referenced nodes and remove the I{href} attribute. @param node: A node to update. @type node: L{Element} @return: The updated node @rtype: L{Element} """ self.replace_references(node) for c in node.children: self.update(c) return node def replace_references(self, node): """ Replacing the I{multiref} references with the contents of the referenced nodes and remove the I{href} attribute. Warning: since the I{ref} is not cloned, @param node: A node to update. @type node: L{Element} """ href = node.getAttribute('href') if href is None: return id = href.getValue() ref = self.catalog.get(id) if ref is None: import logging log = logging.getLogger(__name__) log.error('soap multiref: %s, not-resolved', id) return node.append(ref.children) node.setText(ref.getText()) for a in ref.attributes: if a.name != 'id': node.append(a) node.remove(href) def build_catalog(self, body): """ Create the I{catalog} of multiref nodes by id and the list of non-multiref nodes. @param body: A soap envelope body node. @type body: L{Element} """ for child in body.children: if self.soaproot(child): self.nodes.append(child) id = child.get('id') if id is None: continue key = '#%s' % id self.catalog[key] = child def soaproot(self, node): """ Get whether the specified I{node} is a soap encoded root. This is determined by examining @soapenc:root='1'. The node is considered to be a root when the attribute is not specified. @param node: A node to evaluate. @type node: L{Element} @return: True if a soap encoded root. @rtype: bool """ root = node.getAttribute('root', ns=soapenc) if root is None: return True else: return ( root.value == '1' ) suds-1.1.2/suds/bindings/rpc.py000066400000000000000000000054371425611400200164060ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Classes for the (WS) SOAP I{rpc/literal} and I{rpc/encoded} bindings. """ from suds import * from suds.mx.encoded import Encoded as MxEncoded from suds.umx.encoded import Encoded as UmxEncoded from suds.bindings.binding import Binding, envns from suds.sax.element import Element encns = ("SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/") class RPC(Binding): """RPC/Literal binding style.""" def param_defs(self, method): return self.bodypart_types(method) def envelope(self, header, body): env = super(RPC, self).envelope(header, body) env.addPrefix(encns[0], encns[1]) env.set("%s:encodingStyle" % (envns[0],), encns[1]) return env def bodycontent(self, method, args, kwargs): n = 0 root = self.method(method) for pd in self.param_defs(method): if n < len(args): value = args[n] else: value = kwargs.get(pd[0]) p = self.mkparam(method, pd, value) if p is not None: root.append(p) n += 1 return root def replycontent(self, method, body): return body[0].children def method(self, method): """ Get the document root. For I{rpc/(literal|encoded)}, this is the name of the method qualified by the schema tns. @param method: A service method. @type method: I{service.Method} @return: A root element. @rtype: L{Element} """ ns = method.soap.input.body.namespace if ns[0] is None: ns = ('ns0', ns[1]) return Element(method.name, ns=ns) class Encoded(RPC): """RPC/Encoded (section 5) binding style.""" def marshaller(self): return MxEncoded(self.schema()) def unmarshaller(self): """ Get the appropriate schema based XML decoder. @return: Typed unmarshaller. @rtype: L{UmxTyped} """ return UmxEncoded(self.schema()) suds-1.1.2/suds/builder.py000066400000000000000000000103131425611400200154400ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ The I{builder} module provides an wsdl/xsd defined types factory """ from suds import * from suds.sudsobject import Factory class Builder: """ Builder used to construct an object for types defined in the schema """ def __init__(self, resolver): """ @param resolver: A schema object name resolver. @type resolver: L{resolver.Resolver} """ self.resolver = resolver def build(self, name): """ build a an object for the specified typename as defined in the schema """ if isinstance(name, str): type = self.resolver.find(name) if type is None: raise TypeNotFound(name) else: type = name cls = type.name if type.mixed(): data = Factory.property(cls) else: data = Factory.object(cls) resolved = type.resolve() md = data.__metadata__ md.sxtype = resolved md.ordering = self.ordering(resolved) history = [] self.add_attributes(data, resolved) for child, ancestry in type.children(): if self.skip_child(child, ancestry): continue self.process(data, child, history[:]) return data def process(self, data, type, history): """ process the specified type then process its children """ if type in history: return if type.enum(): return history.append(type) resolved = type.resolve() value = None if type.multi_occurrence(): value = [] else: if len(resolved) > 0: if resolved.mixed(): value = Factory.property(resolved.name) md = value.__metadata__ md.sxtype = resolved else: value = Factory.object(resolved.name) md = value.__metadata__ md.sxtype = resolved md.ordering = self.ordering(resolved) setattr(data, type.name, None if self.skip_value(type) else value) if value is not None: data = value if not isinstance(data, list): self.add_attributes(data, resolved) for child, ancestry in resolved.children(): if self.skip_child(child, ancestry): continue self.process(data, child, history[:]) def add_attributes(self, data, type): """ add required attributes """ for attr, ancestry in type.attributes(): name = '_%s' % attr.name value = attr.get_default() setattr(data, name, value) def skip_child(self, child, ancestry): """ get whether or not to skip the specified child """ if child.any(): return True for x in ancestry: if x.choice(): return True return False def skip_value(self, type): """ whether or not to skip setting the value """ return type.optional() and not type.multi_occurrence() def ordering(self, type): """ get the ordering """ result = [] for child, ancestry in type.resolve(): name = child.name if child.name is None: continue if child.isattr(): name = '_%s' % child.name result.append(name) return result suds-1.1.2/suds/cache.py000066400000000000000000000224641425611400200150670ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Basic caching classes. """ import suds import suds.sax.element import suds.sax.parser import datetime import os try: import pickle as pickle except Exception: import pickle import shutil import tempfile from logging import getLogger log = getLogger(__name__) class Cache(object): """An object cache.""" def get(self, id): """ Get an object from the cache by id. @param id: The object id. @type id: str @return: The object, else None. @rtype: any """ raise Exception("not-implemented") def put(self, id, object): """ Put an object into the cache. @param id: The object id. @type id: str @param object: The object to add. @type object: any """ raise Exception("not-implemented") def purge(self, id): """ Purge an object from the cache by id. @param id: A object id. @type id: str """ raise Exception("not-implemented") def clear(self): """Clear all objects from the cache.""" raise Exception("not-implemented") class NoCache(Cache): """The pass-through object cache.""" def get(self, id): return def put(self, id, object): pass class FileCache(Cache): """ A file-based URL cache. @cvar fnprefix: The file name prefix. @type fnprefix: str @cvar remove_default_location_on_exit: Whether to remove the default cache location on process exit (default=True). @type remove_default_location_on_exit: bool @ivar duration: The duration after which cached entries expire (0=never). @type duration: datetime.timedelta @ivar location: The cached file folder. @type location: str """ fnprefix = "suds" __default_location = None remove_default_location_on_exit = True def __init__(self, location=None, **duration): """ Initialized a new FileCache instance. If no cache location is specified, a temporary default location will be used. Such default cache location will be shared by all FileCache instances with no explicitly specified location within the same process. The default cache location will be removed automatically on process exit unless user sets the remove_default_location_on_exit FileCache class attribute to False. @param location: The cached file folder. @type location: str @param duration: The duration after which cached entries expire (default: 0=never). @type duration: keyword arguments for datetime.timedelta constructor """ if location is None: location = self.__get_default_location() self.location = location self.duration = datetime.timedelta(**duration) self.__check_version() def clear(self): for filename in os.listdir(self.location): path = os.path.join(self.location, filename) if os.path.isdir(path): continue if filename.startswith(self.fnprefix): os.remove(path) log.debug("deleted: %s", path) def fnsuffix(self): """ Get the file name suffix. @return: The suffix. @rtype: str """ return "gcf" def get(self, id): try: f = self._getf(id) try: return f.read() finally: f.close() except Exception: pass def purge(self, id): filename = self.__filename(id) try: os.remove(filename) except Exception: pass def put(self, id, data): try: filename = self.__filename(id) f = self.__open(filename, "wb") try: f.write(data) finally: f.close() return data except Exception: log.debug(id, exc_info=1) return data def _getf(self, id): """Open a cached file with the given id for reading.""" try: filename = self.__filename(id) self.__remove_if_expired(filename) return self.__open(filename, "rb") except Exception: pass def __check_version(self): path = os.path.join(self.location, "version") try: f = self.__open(path) try: version = f.read() finally: f.close() if version != suds.__version__: raise Exception() except Exception: self.clear() f = self.__open(path, "w") try: f.write(suds.__version__) finally: f.close() def __filename(self, id): """Return the cache file name for an entry with a given id.""" suffix = self.fnsuffix() filename = "%s-%s.%s" % (self.fnprefix, id, suffix) return os.path.join(self.location, filename) @staticmethod def __get_default_location(): """ Returns the current process's default cache location folder. The folder is determined lazily on first call. """ if not FileCache.__default_location: tmp = tempfile.mkdtemp("suds-default-cache") FileCache.__default_location = tmp import atexit atexit.register(FileCache.__remove_default_location) return FileCache.__default_location def __mktmp(self): """Create the I{location} folder if it does not already exist.""" try: if not os.path.isdir(self.location): os.makedirs(self.location) except Exception: log.debug(self.location, exc_info=1) return self def __open(self, filename, *args): """Open cache file making sure the I{location} folder is created.""" self.__mktmp() return open(filename, *args) @staticmethod def __remove_default_location(): """ Removes the default cache location folder. This removal may be disabled by setting the remove_default_location_on_exit FileCache class attribute to False. """ if FileCache.remove_default_location_on_exit: # We must not load shutil here on-demand as under some # circumstances this may cause the shutil.rmtree() operation to # fail due to not having some internal module loaded. E.g. this # happens if you run the project's test suite using the setup.py # test command on Python 2.4.x. shutil.rmtree(FileCache.__default_location, ignore_errors=True) def __remove_if_expired(self, filename): """ Remove a cached file entry if it expired. @param filename: The file name. @type filename: str """ if not self.duration: return created = datetime.datetime.fromtimestamp(os.path.getctime(filename)) expired = created + self.duration if expired < datetime.datetime.now(): os.remove(filename) log.debug("%s expired, deleted", filename) class DocumentCache(FileCache): """XML document file cache.""" def fnsuffix(self): return "xml" def get(self, id): fp = None try: fp = self._getf(id) if fp is not None: p = suds.sax.parser.Parser() cached = p.parse(fp) fp.close() return cached except Exception: if fp is not None: fp.close() self.purge(id) def put(self, id, object): if isinstance(object, (suds.sax.document.Document, suds.sax.element.Element)): super(DocumentCache, self).put(id, suds.byte_str(str(object))) return object class ObjectCache(FileCache): """ Pickled object file cache. @cvar protocol: The pickling protocol. @type protocol: int """ protocol = 2 def fnsuffix(self): return "px" def get(self, id): fp = None try: fp = self._getf(id) if fp is not None: cached = pickle.load(fp) fp.close() return cached except Exception: if fp is not None: fp.close() self.purge(id) def put(self, id, object): data = pickle.dumps(object, self.protocol) super(ObjectCache, self).put(id, data) return object suds-1.1.2/suds/client.py000066400000000000000000001004161425611400200152740ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Service proxy implementation providing access to web services. """ import suds from suds import * import suds.bindings.binding from suds.builder import Builder import suds.cache import suds.metrics as metrics from suds.options import Options from suds.plugin import PluginContainer from suds.properties import Unskin from suds.reader import DefinitionsReader from suds.resolver import PathResolver from suds.sax.document import Document import suds.sax.parser from suds.servicedefinition import ServiceDefinition import suds.transport import suds.transport.https from suds.umx.basic import Basic as UmxBasic from suds.wsdl import Definitions from . import sudsobject from http.cookiejar import CookieJar from copy import deepcopy import http.client from logging import getLogger log = getLogger(__name__) class Client(UnicodeMixin): """ A lightweight web service client. @ivar wsdl: The WSDL object. @type wsdl:L{Definitions} @ivar service: The service proxy used to invoke operations. @type service: L{Service} @ivar factory: The factory used to create objects. @type factory: L{Factory} @ivar sd: The service definition @type sd: L{ServiceDefinition} @ivar messages: The last sent/received messages. @type messages: str[2] """ @classmethod def items(cls, sobject): """ Extract I{items} from a suds object. Much like the items() method works on I{dict}. @param sobject: A suds object @type sobject: L{Object} @return: A list of items contained in I{sobject}. @rtype: [(key, value),...] """ return sudsobject.items(sobject) @classmethod def dict(cls, sobject): """ Convert a sudsobject into a dictionary. @param sobject: A suds object @type sobject: L{Object} @return: A dictionary of items contained in I{sobject}. @rtype: dict """ return sudsobject.asdict(sobject) @classmethod def metadata(cls, sobject): """ Extract the metadata from a suds object. @param sobject: A suds object @type sobject: L{Object} @return: The object's metadata @rtype: L{sudsobject.Metadata} """ return sobject.__metadata__ def __init__(self, url, **kwargs): """ @param url: The URL for the WSDL. @type url: str @param kwargs: keyword arguments. @see: L{Options} """ options = Options() options.transport = suds.transport.https.HttpAuthenticated() self.options = options if "cache" not in kwargs: kwargs["cache"] = suds.cache.ObjectCache(days=1) self.set_options(**kwargs) reader = DefinitionsReader(options, Definitions) self.wsdl = reader.open(url) plugins = PluginContainer(options.plugins) plugins.init.initialized(wsdl=self.wsdl) self.factory = Factory(self.wsdl) self.service = ServiceSelector(self, self.wsdl.services) self.sd = [] for s in self.wsdl.services: sd = ServiceDefinition(self.wsdl, s) self.sd.append(sd) self.messages = dict(tx=None, rx=None) def set_options(self, **kwargs): """ Set options. @param kwargs: keyword arguments. @see: L{Options} """ p = Unskin(self.options) p.update(kwargs) def add_prefix(self, prefix, uri): """ Add I{static} mapping of an XML namespace prefix to a namespace. Useful for cases when a WSDL and referenced XSD schemas make heavy use of namespaces and those namespaces are subject to change. @param prefix: An XML namespace prefix. @type prefix: str @param uri: An XML namespace URI. @type uri: str @raise Exception: prefix already mapped. """ root = self.wsdl.root mapped = root.resolvePrefix(prefix, None) if mapped is None: root.addPrefix(prefix, uri) return if mapped[1] != uri: raise Exception('"%s" already mapped as "%s"' % (prefix, mapped)) def last_sent(self): """ Get last sent I{soap} message. @return: The last sent I{soap} message. @rtype: L{Document} """ return self.messages.get('tx') def last_received(self): """ Get last received I{soap} message. @return: The last received I{soap} message. @rtype: L{Document} """ return self.messages.get('rx') def clone(self): """ Get a shallow clone of this object. The clone only shares the WSDL. All other attributes are unique to the cloned object including options. @return: A shallow clone. @rtype: L{Client} """ class Uninitialized(Client): def __init__(self): pass clone = Uninitialized() clone.options = Options() cp = Unskin(clone.options) mp = Unskin(self.options) cp.update(deepcopy(mp)) clone.wsdl = self.wsdl clone.factory = self.factory clone.service = ServiceSelector(clone, self.wsdl.services) clone.sd = self.sd clone.messages = dict(tx=None, rx=None) return clone def __unicode__(self): s = ["\n"] s.append("Suds ( https://fedorahosted.org/suds/ )") s.append(" version: %s" % (suds.__version__,)) if suds.__build__: s.append(" build: %s" % (suds.__build__,)) for sd in self.sd: s.append("\n\n%s" % (str(sd),)) return "".join(s) class Factory: """ A factory for instantiating types defined in the WSDL. @ivar resolver: A schema type resolver. @type resolver: L{PathResolver} @ivar builder: A schema object builder. @type builder: L{Builder} """ def __init__(self, wsdl): """ @param wsdl: A schema object. @type wsdl: L{wsdl.Definitions} """ self.wsdl = wsdl self.resolver = PathResolver(wsdl) self.builder = Builder(self.resolver) def create(self, name): """ Create a WSDL type by name. @param name: The name of a type defined in the WSDL. @type name: str @return: The requested object. @rtype: L{Object} """ timer = metrics.Timer() timer.start() type = self.resolver.find(name) if type is None: raise TypeNotFound(name) if type.enum(): result = sudsobject.Factory.object(name) for e, a in type.children(): setattr(result, e.name, e.name) else: try: result = self.builder.build(type) except Exception as e: log.error("create '%s' failed", name, exc_info=True) raise BuildError(name, e) timer.stop() metrics.log.debug("%s created: %s", name, timer) return result def separator(self, ps): """ Set the path separator. @param ps: The new path separator. @type ps: char """ self.resolver = PathResolver(self.wsdl, ps) class ServiceSelector: """ The B{service} selector is used to select a web service. Most WSDLs only define a single service in which case access by subscript is passed through to a L{PortSelector}. This is also the behavior when a I{default} service has been specified. In cases where multiple services have been defined and no default has been specified, the service is found by name (or index) and a L{PortSelector} for the service is returned. In all cases, attribute access is forwarded to the L{PortSelector} for either the I{first} service or the I{default} service (when specified). @ivar __client: A suds client. @type __client: L{Client} @ivar __services: A list of I{WSDL} services. @type __services: list """ def __init__(self, client, services): """ @param client: A suds client. @type client: L{Client} @param services: A list of I{WSDL} services. @type services: list """ self.__client = client self.__services = services def __getattr__(self, name): """ Attribute access is forwarded to the L{PortSelector}. Uses the I{default} service if specified or the I{first} service otherwise. @param name: Method name. @type name: str @return: A L{PortSelector}. @rtype: L{PortSelector}. """ default = self.__ds() if default is None: port = self.__find(0) else: port = default return getattr(port, name) def __getitem__(self, name): """ Provides I{service} selection by name (string) or index (integer). In cases where only a single service is defined or a I{default} has been specified, the request is forwarded to the L{PortSelector}. @param name: The name (or index) of a service. @type name: int|str @return: A L{PortSelector} for the specified service. @rtype: L{PortSelector}. """ if len(self.__services) == 1: port = self.__find(0) return port[name] default = self.__ds() if default is not None: port = default return port[name] return self.__find(name) def __find(self, name): """ Find a I{service} by name (string) or index (integer). @param name: The name (or index) of a service. @type name: int|str @return: A L{PortSelector} for the found service. @rtype: L{PortSelector}. """ service = None if not self.__services: raise Exception("No services defined") if isinstance(name, int): try: service = self.__services[name] name = service.name except IndexError: raise ServiceNotFound("at [%d]" % (name,)) else: for s in self.__services: if name == s.name: service = s break if service is None: raise ServiceNotFound(name) return PortSelector(self.__client, service.ports, name) def __ds(self): """ Get the I{default} service if defined in the I{options}. @return: A L{PortSelector} for the I{default} service. @rtype: L{PortSelector}. """ ds = self.__client.options.service if ds is not None: return self.__find(ds) class PortSelector: """ The B{port} selector is used to select a I{web service} B{port}. In cases where multiple ports have been defined and no default has been specified, the port is found by name (or index) and a L{MethodSelector} for the port is returned. In all cases, attribute access is forwarded to the L{MethodSelector} for either the I{first} port or the I{default} port (when specified). @ivar __client: A suds client. @type __client: L{Client} @ivar __ports: A list of I{service} ports. @type __ports: list @ivar __qn: The I{qualified} name of the port (used for logging). @type __qn: str """ def __init__(self, client, ports, qn): """ @param client: A suds client. @type client: L{Client} @param ports: A list of I{service} ports. @type ports: list @param qn: The name of the service. @type qn: str """ self.__client = client self.__ports = ports self.__qn = qn def __getattr__(self, name): """ Attribute access is forwarded to the L{MethodSelector}. Uses the I{default} port when specified or the I{first} port otherwise. @param name: The name of a method. @type name: str @return: A L{MethodSelector}. @rtype: L{MethodSelector}. """ default = self.__dp() if default is None: m = self.__find(0) else: m = default return getattr(m, name) def __getitem__(self, name): """ Provides I{port} selection by name (string) or index (integer). In cases where only a single port is defined or a I{default} has been specified, the request is forwarded to the L{MethodSelector}. @param name: The name (or index) of a port. @type name: int|str @return: A L{MethodSelector} for the specified port. @rtype: L{MethodSelector}. """ default = self.__dp() if default is None: return self.__find(name) return default def __find(self, name): """ Find a I{port} by name (string) or index (integer). @param name: The name (or index) of a port. @type name: int|str @return: A L{MethodSelector} for the found port. @rtype: L{MethodSelector}. """ port = None if not self.__ports: raise Exception("No ports defined: %s" % (self.__qn,)) if isinstance(name, int): qn = "%s[%d]" % (self.__qn, name) try: port = self.__ports[name] except IndexError: raise PortNotFound(qn) else: qn = ".".join((self.__qn, name)) for p in self.__ports: if name == p.name: port = p break if port is None: raise PortNotFound(qn) qn = ".".join((self.__qn, port.name)) return MethodSelector(self.__client, port.methods, qn) def __dp(self): """ Get the I{default} port if defined in the I{options}. @return: A L{MethodSelector} for the I{default} port. @rtype: L{MethodSelector}. """ dp = self.__client.options.port if dp is not None: return self.__find(dp) class MethodSelector: """ The B{method} selector is used to select a B{method} by name. @ivar __client: A suds client. @type __client: L{Client} @ivar __methods: A dictionary of methods. @type __methods: dict @ivar __qn: The I{qualified} name of the method (used for logging). @type __qn: str """ def __init__(self, client, methods, qn): """ @param client: A suds client. @type client: L{Client} @param methods: A dictionary of methods. @type methods: dict @param qn: The I{qualified} name of the port. @type qn: str """ self.__client = client self.__methods = methods self.__qn = qn def __getattr__(self, name): """ Get a method by name and return it in an I{execution wrapper}. @param name: The name of a method. @type name: str @return: An I{execution wrapper} for the specified method name. @rtype: L{Method} """ return self[name] def __getitem__(self, name): """ Get a method by name and return it in an I{execution wrapper}. @param name: The name of a method. @type name: str @return: An I{execution wrapper} for the specified method name. @rtype: L{Method} """ m = self.__methods.get(name) if m is None: qn = ".".join((self.__qn, name)) raise MethodNotFound(qn) return Method(self.__client, m) class Method: """ The I{method} (namespace) object. @ivar client: A client object. @type client: L{Client} @ivar method: A I{WSDL} method. @type I{raw} Method. """ def __init__(self, client, method): """ @param client: A client object. @type client: L{Client} @param method: A I{raw} method. @type I{raw} Method. """ self.client = client self.method = method def __call__(self, *args, **kwargs): """Invoke the method.""" clientclass = self.clientclass(kwargs) client = clientclass(self.client, self.method) try: return client.invoke(args, kwargs) except WebFault as e: if self.faults(): raise return http.client.INTERNAL_SERVER_ERROR, e def faults(self): """Get faults option.""" return self.client.options.faults def clientclass(self, kwargs): """Get SOAP client class.""" if _SimClient.simulation(kwargs): return _SimClient return _SoapClient class RequestContext: """ A request context. Returned by a suds Client when invoking a web service operation with the ``nosend`` enabled. Allows the caller to take care of sending the request himself and return back the reply data for further processing. @ivar envelope: The SOAP request envelope. @type envelope: I{bytes} """ def __init__(self, process_reply, envelope): """ @param process_reply: A callback for processing a user defined reply. @type process_reply: I{callable} @param envelope: The SOAP request envelope. @type envelope: I{bytes} """ self.__process_reply = process_reply self.envelope = envelope def process_reply(self, reply, status=None, description=None): """ Re-entry for processing a successful reply. Depending on how the ``retxml`` option is set, may return the SOAP reply XML or process it and return the Python object representing the returned value. @param reply: The SOAP reply envelope. @type reply: I{bytes} @param status: The HTTP status code. @type status: int @param description: Additional status description. @type description: I{bytes} @return: The invoked web service operation return value. @rtype: I{builtin}|I{subclass of} L{Object}|I{bytes}|I{None} """ return self.__process_reply(reply, status, description) class _SoapClient: """ An internal lightweight SOAP based web service operation client. Each instance is constructed for specific web service operation and knows how to: - Construct a SOAP request for it. - Transport a SOAP request for it using a configured transport. - Receive a SOAP reply using a configured transport. - Process the received SOAP reply. Depending on the given suds options, may do all the tasks listed above or may stop the process at an earlier point and return some intermediate result, e.g. the constructed SOAP request or the raw received SOAP reply. See the invoke() method for more detailed information. @ivar service: The target method. @type service: L{Service} @ivar method: A target method. @type method: L{Method} @ivar options: A dictonary of options. @type options: dict @ivar cookiejar: A cookie jar. @type cookiejar: libcookie.CookieJar """ TIMEOUT_ARGUMENT = "__timeout" def __init__(self, client, method): """ @param client: A suds client. @type client: L{Client} @param method: A target method. @type method: L{Method} """ self.client = client self.method = method self.options = client.options self.cookiejar = CookieJar() def invoke(self, args, kwargs): """ Invoke a specified web service method. Depending on how the ``nosend`` & ``retxml`` options are set, may do one of the following: * Return a constructed web service operation SOAP request without sending it to the web service. * Invoke the web service operation and return its SOAP reply XML. * Invoke the web service operation, process its results and return the Python object representing the returned value. When returning a SOAP request, the request is wrapped inside a RequestContext object allowing the user to acquire a corresponding SOAP reply himself and then pass it back to suds for further processing. Constructed request data is automatically processed using registered plugins and serialized into a byte-string. Exact request XML formatting may be affected by the ``prettyxml`` suds option. @param args: A list of args for the method invoked. @type args: list|tuple @param kwargs: Named (keyword) args for the method invoked. @type kwargs: dict @return: SOAP request, SOAP reply or a web service return value. @rtype: L{RequestContext}|I{builtin}|I{subclass of} L{Object}|I{bytes}| I{None} """ timer = metrics.Timer() timer.start() binding = self.method.binding.input timeout = kwargs.pop(_SoapClient.TIMEOUT_ARGUMENT, None) soapenv = binding.get_message(self.method, args, kwargs) timer.stop() method_name = self.method.name metrics.log.debug("message for '%s' created: %s", method_name, timer) timer.start() result = self.send(soapenv, timeout=timeout) timer.stop() metrics.log.debug("method '%s' invoked: %s", method_name, timer) return result def send(self, soapenv, timeout=None): """ Send SOAP message. Depending on how the ``nosend`` & ``retxml`` options are set, may do one of the following: * Return a constructed web service operation request without sending it to the web service. * Invoke the web service operation and return its SOAP reply XML. * Invoke the web service operation, process its results and return the Python object representing the returned value. @param soapenv: A SOAP envelope to send. @type soapenv: L{Document} @return: SOAP request, SOAP reply or a web service return value. @rtype: L{RequestContext}|I{builtin}|I{subclass of} L{Object}|I{bytes}| I{None} """ location = self.__location() log.debug("sending to (%s)\nmessage:\n%s", location, soapenv) self.last_sent(soapenv) plugins = PluginContainer(self.options.plugins) plugins.message.marshalled(envelope=soapenv.root()) if self.options.prettyxml: soapenv = soapenv.str() else: soapenv = soapenv.plain() soapenv = soapenv.encode("utf-8") ctx = plugins.message.sending(envelope=soapenv) soapenv = ctx.envelope if self.options.nosend: return RequestContext(self.process_reply, soapenv) request = suds.transport.Request(location, soapenv, timeout) request.headers = self.__headers() try: timer = metrics.Timer() timer.start() reply = self.options.transport.send(request) timer.stop() metrics.log.debug("waited %s on server reply", timer) except suds.transport.TransportError as e: content = e.fp and e.fp.read() or "" return self.process_reply(content, e.httpcode, tostr(e)) return self.process_reply(reply.message, None, None) def process_reply(self, reply, status, description): """ Process a web service operation SOAP reply. Depending on how the ``retxml`` option is set, may return the SOAP reply XML or process it and return the Python object representing the returned value. @param reply: The SOAP reply envelope. @type reply: I{bytes} @param status: The HTTP status code (None indicates httplib.OK). @type status: int|I{None} @param description: Additional status description. @type description: str @return: The invoked web service operation return value. @rtype: I{builtin}|I{subclass of} L{Object}|I{bytes}|I{None} """ if status is None: status = http.client.OK debug_message = "Reply HTTP status - %d" % (status,) if status in (http.client.ACCEPTED, http.client.NO_CONTENT): log.debug(debug_message) return #TODO: Consider whether and how to allow plugins to handle error, # httplib.ACCEPTED & httplib.NO_CONTENT replies as well as successful # ones. if status == http.client.OK: log.debug("%s\n%s", debug_message, reply) else: log.debug("%s - %s\n%s", debug_message, description, reply) plugins = PluginContainer(self.options.plugins) ctx = plugins.message.received(reply=reply) reply = ctx.reply # SOAP standard states that SOAP errors must be accompanied by HTTP # status code 500 - internal server error: # # From SOAP 1.1 specification: # In case of a SOAP error while processing the request, the SOAP HTTP # server MUST issue an HTTP 500 "Internal Server Error" response and # include a SOAP message in the response containing a SOAP Fault # element (see section 4.4) indicating the SOAP processing error. # # From WS-I Basic profile: # An INSTANCE MUST use a "500 Internal Server Error" HTTP status code # if the response message is a SOAP Fault. replyroot = None if status in (http.client.OK, http.client.INTERNAL_SERVER_ERROR): replyroot = _parse(reply) if len(reply) > 0: self.last_received(replyroot) plugins.message.parsed(reply=replyroot) fault = self.__get_fault(replyroot) if fault: if status != http.client.INTERNAL_SERVER_ERROR: log.warning("Web service reported a SOAP processing fault " "using an unexpected HTTP status code %d. Reporting " "as an internal server error.", status) if self.options.faults: raise WebFault(fault, replyroot) return http.client.INTERNAL_SERVER_ERROR, fault if status != http.client.OK: if self.options.faults: #TODO: Use a more specific exception class here. raise Exception((status, description)) return status, description if self.options.retxml: return reply result = replyroot and self.method.binding.output.get_reply( self.method, replyroot) ctx = plugins.message.unmarshalled(reply=result) result = ctx.reply if self.options.faults: return result return http.client.OK, result def __get_fault(self, replyroot): """ Extract fault information from a SOAP reply. Returns an I{unmarshalled} fault L{Object} or None in case the given XML document does not contain a SOAP element. @param replyroot: A SOAP reply message root XML element or None. @type replyroot: L{Element}|I{None} @return: A fault object. @rtype: L{Object} """ envns = suds.bindings.binding.envns soapenv = replyroot and replyroot.getChild("Envelope", envns) soapbody = soapenv and soapenv.getChild("Body", envns) fault = soapbody and soapbody.getChild("Fault", envns) return fault is not None and UmxBasic().process(fault) def __headers(self): """ Get HTTP headers for a HTTP/HTTPS SOAP request. @return: A dictionary of header/values. @rtype: dict """ action = self.method.soap.action if isinstance(action, str): action = action.encode("utf-8") result = { "Content-Type": "text/xml; charset=utf-8", "SOAPAction": action} result.update(**self.options.headers) log.debug("headers = %s", result) return result def __location(self): """Returns the SOAP request's target location URL.""" return Unskin(self.options).get("location", self.method.location) def last_sent(self, d=None): """ Get or set last SOAP sent messages document To get the last sent document call the function without parameter. To set the last sent message, pass the document as parameter. @param d: A SOAP reply dict message key @type string: I{bytes} @return: The last sent I{soap} message. @rtype: L{Document} """ key = 'tx' messages = self.client.messages if d is None: return messages.get(key) else: messages[key] = d def last_received(self, d=None): """ Get or set last SOAP received messages document To get the last received document call the function without parameter. To set the last sent message, pass the document as parameter. @param d: A SOAP reply dict message key @type string: I{bytes} @return: The last received I{soap} message. @rtype: L{Document} """ key = 'rx' messages = self.client.messages if d is None: return messages.get(key) else: messages[key] = d class _SimClient(_SoapClient): """ Loopback _SoapClient used for SOAP request/reply simulation. Used when a web service operation is invoked with injected SOAP request or reply data. """ __injkey = "__inject" @classmethod def simulation(cls, kwargs): """Get whether injected data has been specified in I{kwargs}.""" return _SimClient.__injkey in kwargs def invoke(self, args, kwargs): """ Invoke a specified web service method. Uses an injected SOAP request/response instead of a regularly constructed/received one. Depending on how the ``nosend`` & ``retxml`` options are set, may do one of the following: * Return a constructed web service operation request without sending it to the web service. * Invoke the web service operation and return its SOAP reply XML. * Invoke the web service operation, process its results and return the Python object representing the returned value. @param args: Positional arguments for the method invoked. @type args: list|tuple @param kwargs: Keyword arguments for the method invoked. @type kwargs: dict @return: SOAP request, SOAP reply or a web service return value. @rtype: L{RequestContext}|I{builtin}|I{subclass of} L{Object}|I{bytes}| I{None} """ simulation = kwargs.pop(self.__injkey) msg = simulation.get("msg") if msg is not None: assert msg.__class__ is suds.byte_str_class return self.send(_parse(msg)) msg = self.method.binding.input.get_message(self.method, args, kwargs) log.debug("inject (simulated) send message:\n%s", msg) reply = simulation.get("reply") if reply is not None: assert reply.__class__ is suds.byte_str_class status = simulation.get("status") description = simulation.get("description") if description is None: description = "injected reply" return self.process_reply(reply, status, description) raise Exception("reply or msg injection parameter expected") def _parse(string): """ Parses given XML document content. Returns the resulting root XML element node or None if the given XML content is empty. @param string: XML document content to parse. @type string: I{bytes} @return: Resulting root XML element node or None. @rtype: L{Element}|I{None} """ if string: return suds.sax.parser.Parser().parse(string=string) suds-1.1.2/suds/metrics.py000066400000000000000000000037111425611400200154640ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ The I{metrics} module defines classes and other resources designed for collecting and reporting performance metrics. """ import time from suds import * from math import modf from logging import getLogger log = getLogger(__name__) class Timer: def __init__(self): self.started = 0 self.stopped = 0 def start(self): self.started = time.time() self.stopped = 0 return self def stop(self): if self.started > 0: self.stopped = time.time() return self def duration(self): return ( self.stopped - self.started ) def __str__(self): if self.started == 0: return 'not-running' if self.started > 0 and self.stopped == 0: return 'started: %d (running)' % self.started duration = self.duration() jmod = ( lambda m : (m[1], m[0]*1000) ) if duration < 1: ms = (duration*1000) return '%d (ms)' % ms if duration < 60: m = modf(duration) return '%d.%.3d (seconds)' % jmod(m) m = modf(duration/60) return '%d.%.3d (minutes)' % jmod(m) suds-1.1.2/suds/mx/000077500000000000000000000000001425611400200140665ustar00rootroot00000000000000suds-1.1.2/suds/mx/__init__.py000066400000000000000000000034421425611400200162020ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Provides modules containing classes to support marshalling to XML. """ from suds.sudsobject import Object class Content(Object): """ Marshaller content. @ivar tag: The content tag. @type tag: str @ivar value: The content's value. @type value: I{any} """ extensions = [] def __init__(self, tag=None, value=None, **kwargs): """ @param tag: The content tag. @type tag: str @param value: The content's value. @type value: I{any} """ Object.__init__(self) self.tag = tag self.value = value for k, v in kwargs.items(): setattr(self, k, v) def __getattr__(self, name): try: return self.__dict__[name] except KeyError: pass if name in self.extensions: value = None setattr(self, name, value) return value raise AttributeError("Content has no attribute %s" % (name,)) suds-1.1.2/suds/mx/appender.py000066400000000000000000000204041425611400200162360ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Provides appender classes for I{marshalling}. """ from suds import * from suds.mx import * from suds.sudsobject import Object, Property from suds.sax.element import Element from suds.sax.text import Text from suds.xsd.sxbasic import Attribute class Matcher: """ Appender matcher. @ivar cls: A class object. @type cls: I{classobj} """ def __init__(self, cls): """ @param cls: A class object. @type cls: I{classobj} """ self.cls = cls def __eq__(self, x): if self.cls is None: return x is None return isinstance(x, self.cls) class ContentAppender: """ Appender used to add content to marshalled objects. @ivar default: The default appender. @type default: L{Appender} @ivar appenders: A I{table} of appenders mapped by class. @type appenders: I{table} """ def __init__(self, marshaller): """ @param marshaller: A marshaller. @type marshaller: L{suds.mx.core.Core} """ self.default = PrimitiveAppender(marshaller) self.appenders = ( (Matcher(None), NoneAppender(marshaller)), (Matcher(null), NoneAppender(marshaller)), (Matcher(Property), PropertyAppender(marshaller)), (Matcher(Object), ObjectAppender(marshaller)), (Matcher(Element), ElementAppender(marshaller)), (Matcher(Text), TextAppender(marshaller)), (Matcher(list), ListAppender(marshaller)), (Matcher(tuple), ListAppender(marshaller))) def append(self, parent, content): """ Select an appender and append the content to parent. @param parent: A parent node. @type parent: L{Element} @param content: The content to append. @type content: L{Content} """ appender = self.default for matcher, candidate_appender in self.appenders: if matcher == content.value: appender = candidate_appender break appender.append(parent, content) class Appender: """ An appender used by the marshaller to append content. @ivar marshaller: A marshaller. @type marshaller: L{suds.mx.core.Core} """ def __init__(self, marshaller): """ @param marshaller: A marshaller. @type marshaller: L{suds.mx.core.Core} """ self.marshaller = marshaller def node(self, content): """ Create and return an XML node that is qualified using the I{type}. Also, make sure all referenced namespace prefixes are declared. @param content: The content for which processing has ended. @type content: L{Object} @return: A new node. @rtype: L{Element} """ return self.marshaller.node(content) def setnil(self, node, content): """ Set the value of the I{node} to nill. @param node: A I{nil} node. @type node: L{Element} @param content: The content for which processing has ended. @type content: L{Object} """ self.marshaller.setnil(node, content) def setdefault(self, node, content): """ Set the value of the I{node} to a default value. @param node: A I{nil} node. @type node: L{Element} @param content: The content for which processing has ended. @type content: L{Object} @return: The default. """ return self.marshaller.setdefault(node, content) def optional(self, content): """ Get whether the specified content is optional. @param content: The content which to check. @type content: L{Content} """ return self.marshaller.optional(content) def suspend(self, content): """ Notify I{marshaller} that appending this content has suspended. @param content: The content for which processing has been suspended. @type content: L{Object} """ self.marshaller.suspend(content) def resume(self, content): """ Notify I{marshaller} that appending this content has resumed. @param content: The content for which processing has been resumed. @type content: L{Object} """ self.marshaller.resume(content) def append(self, parent, content): """ Append the specified L{content} to the I{parent}. @param content: The content to append. @type content: L{Object} """ self.marshaller.append(parent, content) class PrimitiveAppender(Appender): """ An appender for python I{primitive} types. """ def append(self, parent, content): if content.tag.startswith('_') and isinstance(content.type, Attribute): attr = content.tag[1:] value = tostr(content.value) if value: parent.set(attr, value) else: child = self.node(content) child.setText(tostr(content.value)) parent.append(child) class NoneAppender(Appender): """ An appender for I{None} values. """ def append(self, parent, content): child = self.node(content) default = self.setdefault(child, content) if default is None: self.setnil(child, content) parent.append(child) class PropertyAppender(Appender): """ A L{Property} appender. """ def append(self, parent, content): p = content.value child = self.node(content) child.setText(p.get()) parent.append(child) for item in list(p.items()): cont = Content(tag=item[0], value=item[1]) Appender.append(self, child, cont) class ObjectAppender(Appender): """ An L{Object} appender. """ def append(self, parent, content): object = content.value child = self.node(content) parent.append(child) for item in object: cont = Content(tag=item[0], value=item[1]) Appender.append(self, child, cont) class ElementWrapper(Element): """ Element wrapper. """ def __init__(self, content): Element.__init__(self, content.name, content.parent) self.__content = content def str(self, indent=0): return self.__content.str(indent) class ElementAppender(Appender): """ An appender for I{Element} types. """ def append(self, parent, content): if content.tag.startswith('_') and isinstance(content.type, Attribute): raise Exception('raw XML not valid as attribute value') child = ElementWrapper(content.value) parent.append(child) class ListAppender(Appender): """ A list/tuple appender. """ def append(self, parent, content): collection = content.value if len(collection): self.suspend(content) for item in collection: cont = Content(tag=content.tag, value=item) Appender.append(self, parent, cont) self.resume(content) class TextAppender(Appender): """ An appender for I{Text} values. """ def append(self, parent, content): if content.tag.startswith('_') and isinstance(content.type, Attribute): attr = content.tag[1:] value = tostr(content.value) if value: parent.set(attr, value) else: child = self.node(content) child.setText(content.value) parent.append(child) suds-1.1.2/suds/mx/basic.py000066400000000000000000000030741425611400200155250ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Provides basic I{marshaller} classes. """ from suds import * from suds.mx import * from suds.mx.core import Core class Basic(Core): """ A I{basic} (untyped) marshaller. """ def process(self, value, tag=None): """ Process (marshal) the tag with the specified value using the optional type information. @param value: The value (content) of the XML node. @type value: (L{Object}|any) @param tag: The (optional) tag name for the value. The default is value.__class__.__name__ @type tag: str @return: An xml node. @rtype: L{Element} """ content = Content(tag=tag, value=value) result = Core.process(self, content) return result suds-1.1.2/suds/mx/core.py000066400000000000000000000107321425611400200153730ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Provides I{marshaller} core classes. """ from suds import * from suds.mx import * from suds.mx.appender import ContentAppender from suds.sax.document import Document from logging import getLogger log = getLogger(__name__) class Core: """ An I{abstract} marshaller. This class implement the core functionality of the marshaller. @ivar appender: A content appender. @type appender: L{ContentAppender} """ def __init__(self): """ """ self.appender = ContentAppender(self) def process(self, content): """ Process (marshal) the tag with the specified value using the optional type information. @param content: The content to process. @type content: L{Object} """ log.debug('processing:\n%s', content) self.reset() if content.tag is None: content.tag = content.value.__class__.__name__ document = Document() self.append(document, content) return document.root() def append(self, parent, content): """ Append the specified L{content} to the I{parent}. @param parent: The parent node to append to. @type parent: L{Element} @param content: The content to append. @type content: L{Object} """ log.debug('appending parent:\n%s\ncontent:\n%s', parent, content) if self.start(content): self.appender.append(parent, content) self.end(parent, content) def reset(self): """ Reset the marshaller. """ pass def node(self, content): """ Create and return an XML node. @param content: Content information for the new node. @type content: L{Content} @return: An element. @rtype: L{Element} """ raise NotImplementedError def start(self, content): """ Appending this content has started. @param content: The content for which processing has started. @type content: L{Content} @return: True to continue appending @rtype: boolean """ return True def suspend(self, content): """ Appending this content has suspended. @param content: The content for which processing has been suspended. @type content: L{Content} """ pass def resume(self, content): """ Appending this content has resumed. @param content: The content for which processing has been resumed. @type content: L{Content} """ pass def end(self, parent, content): """ Appending this content has ended. @param parent: The parent node ending. @type parent: L{Element} @param content: The content for which processing has ended. @type content: L{Content} """ pass def setnil(self, node, content): """ Set the value of the I{node} to nill. @param node: A I{nil} node. @type node: L{Element} @param content: The content to set nil. @type content: L{Content} """ pass def setdefault(self, node, content): """ Set the value of the I{node} to a default value. @param node: A I{nil} node. @type node: L{Element} @param content: The content to set the default value. @type content: L{Content} @return: The default. """ pass def optional(self, content): """ Get whether the specified content is optional. @param content: The content which to check. @type content: L{Content} """ return False suds-1.1.2/suds/mx/encoded.py000066400000000000000000000107251425611400200160460ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Provides encoded I{marshaller} classes. """ from suds import * from suds.mx import * from suds.mx.literal import Literal from suds.mx.typer import Typer from suds.sudsobject import Factory, Object from suds.xsd.query import TypeQuery # # Add encoded extensions # aty = The soap (section 5) encoded array type. # Content.extensions.append('aty') class Encoded(Literal): """ A SOAP section (5) encoding marshaller. This marshaller supports rpc/encoded soap styles. """ def start(self, content): # # For soap encoded arrays, the 'aty' (array type) information # is extracted and added to the 'content'. Then, the content.value # is replaced with an object containing an 'item=[]' attribute # containing values that are 'typed' suds objects. # start = Literal.start(self, content) if start and isinstance(content.value, (list,tuple)): resolved = content.type.resolve() for c in resolved: if hasattr(c[0], 'aty'): content.aty = (content.tag, c[0].aty) self.cast(content) break return start def end(self, parent, content): # # For soap encoded arrays, the soapenc:arrayType attribute is # added with proper type and size information. # Eg: soapenc:arrayType="xs:int[3]" # Literal.end(self, parent, content) if content.aty is None: return tag, aty = content.aty ns0 = ('at0', aty[1]) ns1 = ('at1', 'http://schemas.xmlsoap.org/soap/encoding/') array = content.value.item child = parent.getChild(tag) child.addPrefix(ns0[0], ns0[1]) child.addPrefix(ns1[0], ns1[1]) name = '%s:arrayType' % ns1[0] value = '%s:%s[%d]' % (ns0[0], aty[0], len(array)) child.set(name, value) def encode(self, node, content): if content.type.any(): Typer.auto(node, content.value) return if content.real.any(): Typer.auto(node, content.value) return ns = None name = content.real.name if self.xstq: ns = content.real.namespace() Typer.manual(node, name, ns) def cast(self, content): """ Cast the I{untyped} list items found in content I{value}. Each items contained in the list is checked for XSD type information. Items (values) that are I{untyped}, are replaced with suds objects and type I{metadata} is added. @param content: The content holding the collection. @type content: L{Content} @return: self @rtype: L{Encoded} """ aty = content.aty[1] resolved = content.type.resolve() array = Factory.object(resolved.name) array.item = [] query = TypeQuery(aty) ref = query.execute(self.schema) if ref is None: raise TypeNotFound(ref) for x in content.value: if isinstance(x, (list, tuple)): array.item.append(x) continue if isinstance(x, Object): md = x.__metadata__ md.sxtype = ref array.item.append(x) continue if isinstance(x, dict): x = Factory.object(ref.name, x) md = x.__metadata__ md.sxtype = ref array.item.append(x) continue x = Factory.property(ref.name, x) md = x.__metadata__ md.sxtype = ref array.item.append(x) content.value = array return self suds-1.1.2/suds/mx/literal.py000066400000000000000000000223361425611400200161020ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Provides literal I{marshaller} classes. """ from suds import * from suds.mx import * from suds.mx.core import Core from suds.mx.typer import Typer from suds.resolver import Frame, GraphResolver from suds.sax.element import Element from suds.sudsobject import Factory from logging import getLogger log = getLogger(__name__) # add typed extensions Content.extensions.append("type") # The expected xsd type Content.extensions.append("real") # The 'true' XSD type Content.extensions.append("ancestry") # The 'type' ancestry class Typed(Core): """ A I{typed} marshaller. This marshaller is semi-typed as needed to support both I{document/literal} and I{rpc/literal} SOAP message styles. @ivar schema: An XSD schema. @type schema: L{xsd.schema.Schema} @ivar resolver: A schema type resolver. @type resolver: L{GraphResolver} """ def __init__(self, schema, xstq=True): """ @param schema: A schema object @type schema: L{xsd.schema.Schema} @param xstq: The B{x}ml B{s}chema B{t}ype B{q}ualified flag indicates that the I{xsi:type} attribute values should be qualified by namespace. @type xstq: bool """ Core.__init__(self) self.schema = schema self.xstq = xstq self.resolver = GraphResolver(self.schema) def reset(self): self.resolver.reset() def start(self, content): """ Start marshalling the 'content' by ensuring that both the 'content' _and_ the resolver are primed with the XSD type information. The 'content' value is both translated and sorted based on the XSD type. Only values that are objects have their attributes sorted. """ log.debug("starting content:\n%s", content) if content.type is None: name = content.tag if name.startswith("_"): name = "@" + name[1:] content.type = self.resolver.find(name, content.value) if content.type is None: raise TypeNotFound(content.tag) else: known = None if isinstance(content.value, Object): known = self.resolver.known(content.value) if known is None: log.debug("object %s has no type information", content.value) known = content.type frame = Frame(content.type, resolved=known) self.resolver.push(frame) frame = self.resolver.top() content.real = frame.resolved content.ancestry = frame.ancestry self.translate(content) self.sort(content) if self.skip(content): log.debug("skipping (optional) content:\n%s", content) self.resolver.pop() return False return True def suspend(self, content): """ Suspend to process list content. Primarily, this involves popping the 'list' content off the resolver's stack so its list items can be marshalled. """ self.resolver.pop() def resume(self, content): """ Resume processing list content. To do this, we really need to simply push the 'list' content back onto the resolver stack. """ self.resolver.push(Frame(content.type)) def end(self, parent, content): """ End processing the content. Make sure the content ending matches the top of the resolver stack since for list processing we play games with the resolver stack. """ log.debug("ending content:\n%s", content) current = self.resolver.top().type if current != content.type: raise Exception("content (end) mismatch: top=(%s) cont=(%s)" % ( current, content)) self.resolver.pop() def node(self, content): """ Create an XML node. The XML node is namespace qualified as defined by the corresponding schema element. """ ns = content.type.namespace() if content.type.form_qualified: node = Element(content.tag, ns=ns) if ns[0]: node.addPrefix(ns[0], ns[1]) else: node = Element(content.tag) self.encode(node, content) log.debug("created - node:\n%s", node) return node def setnil(self, node, content): """ Set the 'node' nil only if the XSD type specifies that it is permitted. """ if content.type.nillable: node.setnil() def setdefault(self, node, content): """Set the node to the default value specified by the XSD type.""" default = content.type.default if default is not None: node.setText(default) return default def optional(self, content): if content.type.optional(): return True for a in content.ancestry: if a.optional(): return True return False def encode(self, node, content): """ Add (SOAP) encoding information if needed. The encoding information is added only if the resolved type is derived by extension. Furthermore, the xsi:type value is qualified by namespace only if the content (tag) and referenced type are in different namespaces. """ if content.type.any(): return if not content.real.extension(): return if content.type.resolve() == content.real: return ns = None name = content.real.name if self.xstq: ns = content.real.namespace("ns1") Typer.manual(node, name, ns) def skip(self, content): """ Get whether to skip this I{content}. Should be skipped when the content is optional and value is either None or an empty list. @param content: Content to skip. @type content: L{Object} @return: True if content is to be skipped. @rtype: bool """ if self.optional(content): v = content.value if v is None: return True if isinstance(v, (list, tuple)) and not v: return True return False def optional(self, content): if content.type.optional(): return True for a in content.ancestry: if a.optional(): return True return False def translate(self, content): """ Translate using the XSD type information. Python I{dict} is translated to a suds object. Most importantly, primitive values are translated from python to XML types using the XSD type. @param content: Content to translate. @type content: L{Object} @return: self @rtype: L{Typed} """ v = content.value if v is None: return if isinstance(v, dict): cls = content.real.name content.value = Factory.object(cls, v) md = content.value.__metadata__ md.sxtype = content.type return v = content.real.translate(v, False) content.value = v return self def sort(self, content): """ Sort suds object attributes. The attributes are sorted based on the ordering defined in the XSD type information. @param content: Content to sort. @type content: L{Object} @return: self @rtype: L{Typed} """ v = content.value if isinstance(v, Object): md = v.__metadata__ md.ordering = self.ordering(content.real) return self def ordering(self, type): """ Attribute ordering defined in the specified XSD type information. @param type: XSD type object. @type type: L{SchemaObject} @return: An ordered list of attribute names. @rtype: list """ result = [] for child, ancestry in type.resolve(): name = child.name if child.name is None: continue if child.isattr(): name = "_%s" % (child.name,) result.append(name) return result class Literal(Typed): """ A I{literal} marshaller. This marshaller is semi-typed as needed to support both I{document/literal} and I{rpc/literal} soap message styles. """ pass suds-1.1.2/suds/mx/typer.py000066400000000000000000000077261425611400200156170ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Provides XSD typing classes. """ from suds.sax import Namespace from suds.sax.text import Text from suds.sudsobject import Object class Typer: """ Provides XML node typing as either automatic or manual. @cvar types: Class to XSD type mapping. @type types: dict """ types = { bool: ("boolean", Namespace.xsdns), float: ("float", Namespace.xsdns), int: ("int", Namespace.xsdns), int: ("long", Namespace.xsdns), str: ("string", Namespace.xsdns), Text: ("string", Namespace.xsdns), str: ("string", Namespace.xsdns)} @classmethod def auto(cls, node, value=None): """ Automatically set the node's xsi:type attribute based on either I{value}'s or the node text's class. When I{value} is an unmapped class, the default type (xs:any) is set. @param node: XML node. @type node: L{sax.element.Element} @param value: Object that is or would be the node's text. @type value: I{any} @return: Specified node. @rtype: L{sax.element.Element} """ if value is None: value = node.getText() if isinstance(value, Object): known = cls.known(value) if known.name is None: return node tm = known.name, known.namespace() else: tm = cls.types.get(value.__class__, cls.types.get(str)) cls.manual(node, *tm) return node @classmethod def manual(cls, node, tval, ns=None): """ Set the node's xsi:type attribute based on either I{value}'s or the node text's class. Then adds the referenced prefix(s) to the node's prefix mapping. @param node: XML node. @type node: L{sax.element.Element} @param tval: XSD schema type name. @type tval: str @param ns: I{tval} XML namespace. @type ns: (prefix, URI) @return: Specified node. @rtype: L{sax.element.Element} """ xta = ":".join((Namespace.xsins[0], "type")) node.addPrefix(Namespace.xsins[0], Namespace.xsins[1]) if ns is None: node.set(xta, tval) else: ns = cls.genprefix(node, ns) qname = ":".join((ns[0], tval)) node.set(xta, qname) node.addPrefix(ns[0], ns[1]) return node @classmethod def genprefix(cls, node, ns): """ Generate a prefix. @param node: XML node on which the prefix will be used. @type node: L{sax.element.Element} @param ns: Namespace needing a unique prefix. @type ns: (prefix, URI) @return: I{ns} with a new prefix. @rtype: (prefix, URI) """ for i in range(1, 1024): prefix = "ns%d" % (i,) uri = node.resolvePrefix(prefix, default=None) if uri in (None, ns[1]): return prefix, ns[1] raise Exception("auto prefix, exhausted") @classmethod def known(cls, object): try: md = object.__metadata__ known = md.sxtype return known except Exception: pass suds-1.1.2/suds/options.py000066400000000000000000000157611425611400200155210ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Suds basic options classes. """ from suds.cache import Cache, NoCache from suds.properties import * from suds.store import DocumentStore, defaultDocumentStore from suds.transport import Transport from suds.wsse import Security from suds.xsd.doctor import Doctor class TpLinker(AutoLinker): """ Transport (auto) linker used to manage linkage between transport objects Properties and those Properties that contain them. """ def updated(self, properties, prev, next): if isinstance(prev, Transport): tp = Unskin(prev.options) properties.unlink(tp) if isinstance(next, Transport): tp = Unskin(next.options) properties.link(tp) class Options(Skin): """ Options: - B{cache} - The XML document cache. May be set to None for no caching. - type: L{Cache} - default: L{NoCache()} - B{documentStore} - The XML document store used to access locally stored documents without having to download them from an external location. May be set to None for no internal suds library document store. - type: L{DocumentStore} - default: L{defaultDocumentStore} - B{extraArgumentErrors} - Raise exceptions when unknown message parts are detected when receiving a web service reply, compared to the operation's WSDL schema definition. - type: I{bool} - default: True - B{allowUnknownMessageParts} - Raise exceptions when extra arguments are detected when invoking a web service operation, compared to the operation's WSDL schema definition. - type: I{bool} - default: False - B{faults} - Raise faults raised by server, else return tuple from service method invocation as (httpcode, object). - type: I{bool} - default: True - B{service} - The default service name. - type: I{str} - default: None - B{port} - The default service port name, not tcp port. - type: I{str} - default: None - B{location} - This overrides the service port address I{URL} defined in the WSDL. - type: I{str} - default: None - B{transport} - The message transport. - type: L{Transport} - default: None - B{soapheaders} - The soap headers to be included in the soap message. - type: I{any} - default: None - B{wsse} - The web services I{security} provider object. - type: L{Security} - default: None - B{doctor} - A schema I{doctor} object. - type: L{Doctor} - default: None - B{xstq} - The B{x}ml B{s}chema B{t}ype B{q}ualified flag indicates that the I{xsi:type} attribute values should be qualified by namespace. - type: I{bool} - default: True - B{prefixes} - Elements of the soap message should be qualified (when needed) using XML prefixes as opposed to xmlns="" syntax. - type: I{bool} - default: True - B{retxml} - Flag that causes the I{raw} soap envelope to be returned instead of the python object graph. - type: I{bool} - default: False - B{prettyxml} - Flag that causes I{pretty} xml to be rendered when generating the outbound soap envelope. - type: I{bool} - default: False - B{autoblend} - Flag that ensures that the schema(s) defined within the WSDL import each other. - type: I{bool} - default: False - B{cachingpolicy} - The caching policy. - type: I{int} - 0 = Cache XML documents. - 1 = Cache WSDL (pickled) object. - default: 0 - B{plugins} - A plugin container. - type: I{list} - default: I{list()} - B{nosend} - Create the soap envelope but do not send. When specified, method invocation returns a I{RequestContext} instead of sending it. - type: I{bool} - default: False - B{unwrap} - Enable automatic parameter unwrapping when possible. Enabled by default. If disabled, no input or output parameters are ever automatically unwrapped. - type: I{bool} - default: True - B{sortNamespaces} - Namespaces are sorted alphabetically. If disabled, namespaces are left in the order they are received from the source. Enabled by default for historical purposes. - type: I{bool} - default: True """ def __init__(self, **kwargs): domain = __name__ definitions = [ Definition('cache', Cache, NoCache()), Definition('documentStore', DocumentStore, defaultDocumentStore), Definition('extraArgumentErrors', bool, True), Definition('allowUnknownMessageParts', bool, False), Definition('faults', bool, True), Definition('transport', Transport, None, TpLinker()), Definition('service', (int, str), None), Definition('port', (int, str), None), Definition('location', str, None), Definition('soapheaders', (), ()), Definition('wsse', Security, None), Definition('doctor', Doctor, None), Definition('xstq', bool, True), Definition('prefixes', bool, True), Definition('retxml', bool, False), Definition('prettyxml', bool, False), Definition('autoblend', bool, False), Definition('cachingpolicy', int, 0), Definition('plugins', (list, tuple), []), Definition('nosend', bool, False), Definition('unwrap', bool, True), Definition('sortNamespaces', bool, True)] Skin.__init__(self, domain, definitions, kwargs) suds-1.1.2/suds/plugin.py000066400000000000000000000154151425611400200153200ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ The plugin module provides suds plugin implementation classes. """ from suds import * from logging import getLogger log = getLogger(__name__) class Context(object): """Plugin context.""" pass class InitContext(Context): """ Init Context. @ivar wsdl: The WSDL. @type wsdl: L{wsdl.Definitions} """ pass class DocumentContext(Context): """ The XML document load context. @ivar url: The URL. @type url: str @ivar document: Either the XML text or the B{parsed} document root. @type document: (str|L{sax.element.Element}) """ pass class MessageContext(Context): """ The context for sending the SOAP envelope. @ivar envelope: The SOAP envelope to be sent. @type envelope: (str|L{sax.element.Element}) @ivar reply: The reply. @type reply: (str|L{sax.element.Element}|object) """ pass class Plugin: """Plugin base.""" pass class InitPlugin(Plugin): """Base class for all suds I{init} plugins.""" def initialized(self, context): """ Suds client initialization. Called after WSDL the has been loaded. Provides the plugin with the opportunity to inspect/modify the WSDL. @param context: The init context. @type context: L{InitContext} """ pass class DocumentPlugin(Plugin): """Base class for suds I{document} plugins.""" def loaded(self, context): """ Suds has loaded a WSDL/XSD document. Provides the plugin with an opportunity to inspect/modify the unparsed document. Called after each WSDL/XSD document is loaded. @param context: The document context. @type context: L{DocumentContext} """ pass def parsed(self, context): """ Suds has parsed a WSDL/XSD document. Provides the plugin with an opportunity to inspect/modify the parsed document. Called after each WSDL/XSD document is parsed. @param context: The document context. @type context: L{DocumentContext} """ pass class MessagePlugin(Plugin): """Base class for suds I{SOAP message} plugins.""" def marshalled(self, context): """ Suds is about to send the specified SOAP envelope. Provides the plugin with the opportunity to inspect/modify the envelope Document before it is sent. @param context: The send context. The I{envelope} is the envelope document. @type context: L{MessageContext} """ pass def sending(self, context): """ Suds is about to send the specified SOAP envelope. Provides the plugin with the opportunity to inspect/modify the message text before it is sent. @param context: The send context. The I{envelope} is the envelope text. @type context: L{MessageContext} """ pass def received(self, context): """ Suds has received the specified reply. Provides the plugin with the opportunity to inspect/modify the received XML text before it is SAX parsed. @param context: The reply context. The I{reply} is the raw text. @type context: L{MessageContext} """ pass def parsed(self, context): """ Suds has SAX parsed the received reply. Provides the plugin with the opportunity to inspect/modify the SAX parsed DOM tree for the reply before it is unmarshalled. @param context: The reply context. The I{reply} is DOM tree. @type context: L{MessageContext} """ pass def unmarshalled(self, context): """ Suds has unmarshalled the received reply. Provides the plugin with the opportunity to inspect/modify the unmarshalled reply object before it is returned. @param context: The reply context. The I{reply} is unmarshalled suds object. @type context: L{MessageContext} """ pass class PluginContainer: """ Plugin container provides easy method invocation. @ivar plugins: A list of plugin objects. @type plugins: [L{Plugin},] @cvar ctxclass: A dict of plugin method / context classes. @type ctxclass: dict """ domains = { 'init': (InitContext, InitPlugin), 'document': (DocumentContext, DocumentPlugin), 'message': (MessageContext, MessagePlugin)} def __init__(self, plugins): """ @param plugins: A list of plugin objects. @type plugins: [L{Plugin},] """ self.plugins = plugins def __getattr__(self, name): domain = self.domains.get(name) if not domain: raise Exception('plugin domain (%s), invalid' % (name,)) ctx, pclass = domain plugins = [p for p in self.plugins if isinstance(p, pclass)] return PluginDomain(ctx, plugins) class PluginDomain: """ The plugin domain. @ivar ctx: A context. @type ctx: L{Context} @ivar plugins: A list of plugins (targets). @type plugins: list """ def __init__(self, ctx, plugins): self.ctx = ctx self.plugins = plugins def __getattr__(self, name): return Method(name, self) class Method: """ Plugin method. @ivar name: The method name. @type name: str @ivar domain: The plugin domain. @type domain: L{PluginDomain} """ def __init__(self, name, domain): """ @param name: The method name. @type name: str @param domain: A plugin domain. @type domain: L{PluginDomain} """ self.name = name self.domain = domain def __call__(self, **kwargs): ctx = self.domain.ctx() ctx.__dict__.update(kwargs) for plugin in self.domain.plugins: method = getattr(plugin, self.name, None) if method and callable(method): method(ctx) return ctx suds-1.1.2/suds/properties.py000066400000000000000000000371071425611400200162200ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Properties classes. """ class AutoLinker(object): """ Base class, provides interface for I{automatic} link management between a L{Properties} object and the L{Properties} contained within I{values}. """ def updated(self, properties, prev, next): """ Notification that a values was updated and the linkage between the I{properties} contained with I{prev} need to be relinked to the L{Properties} contained within the I{next} value. """ pass class Link(object): """ Property link object. @ivar endpoints: A tuple of the (2) endpoints of the link. @type endpoints: tuple(2) """ def __init__(self, a, b): """ @param a: Property (A) to link. @type a: L{Property} @param b: Property (B) to link. @type b: L{Property} """ pA = Endpoint(self, a) pB = Endpoint(self, b) self.endpoints = (pA, pB) self.validate(a, b) a.links.append(pB) b.links.append(pA) def validate(self, pA, pB): """ Validate that the two properties may be linked. @param pA: Endpoint (A) to link. @type pA: L{Endpoint} @param pB: Endpoint (B) to link. @type pB: L{Endpoint} @return: self @rtype: L{Link} """ if pA in pB.links or \ pB in pA.links: raise Exception('Already linked') dA = pA.domains() dB = pB.domains() for d in dA: if d in dB: raise Exception('Duplicate domain "%s" found' % d) for d in dB: if d in dA: raise Exception('Duplicate domain "%s" found' % d) kA = list(pA.keys()) kB = list(pB.keys()) for k in kA: if k in kB: raise Exception('Duplicate key %s found' % k) for k in kB: if k in kA: raise Exception('Duplicate key %s found' % k) return self def teardown(self): """ Teardown the link. Removes endpoints from properties I{links} collection. @return: self @rtype: L{Link} """ pA, pB = self.endpoints if pA in pB.links: pB.links.remove(pA) if pB in pA.links: pA.links.remove(pB) return self class Endpoint(object): """ Link endpoint (wrapper). @ivar link: The associated link. @type link: L{Link} @ivar target: The properties object. @type target: L{Property} """ def __init__(self, link, target): self.link = link self.target = target def teardown(self): return self.link.teardown() def __eq__(self, rhs): return ( self.target == rhs ) def __hash__(self): return hash(self.target) def __getattr__(self, name): return getattr(self.target, name) class Definition: """ Property definition. @ivar name: The property name. @type name: str @ivar classes: The (class) list of permitted values @type classes: tuple @ivar default: The default value. @ivar type: any """ def __init__(self, name, classes, default, linker=AutoLinker()): """ @param name: The property name. @type name: str @param classes: The (class) list of permitted values @type classes: tuple @param default: The default value. @type default: any """ if not isinstance(classes, (list, tuple)): classes = (classes,) self.name = name self.classes = classes self.default = default self.linker = linker def nvl(self, value=None): """ Convert the I{value} into the default when I{None}. @param value: The proposed value. @type value: any @return: The I{default} when I{value} is I{None}, else I{value}. @rtype: any """ if value is None: return self.default else: return value def validate(self, value): """ Validate the I{value} is of the correct class. @param value: The value to validate. @type value: any @raise AttributeError: When I{value} is invalid. """ if value is None: return if len(self.classes) and \ not isinstance(value, self.classes): msg = '"%s" must be: %s' % (self.name, self.classes) raise AttributeError(msg) def __repr__(self): return '%s: %s' % (self.name, str(self)) def __str__(self): s = [] if len(self.classes): s.append('classes=%s' % str(self.classes)) else: s.append('classes=*') s.append("default=%s" % str(self.default)) return ', '.join(s) class Properties: """ Represents basic application properties. Provides basic type validation, default values and link/synchronization behavior. @ivar domain: The domain name. @type domain: str @ivar definitions: A table of property definitions. @type definitions: {name: L{Definition}} @ivar links: A list of linked property objects used to create a network of properties. @type links: [L{Property},..] @ivar defined: A dict of property values. @type defined: dict """ def __init__(self, domain, definitions, kwargs): """ @param domain: The property domain name. @type domain: str @param definitions: A table of property definitions. @type definitions: {name: L{Definition}} @param kwargs: A list of property name/values to set. @type kwargs: dict """ self.definitions = {} for d in definitions: self.definitions[d.name] = d self.domain = domain self.links = [] self.defined = {} self.modified = set() self.prime() self.update(kwargs) def definition(self, name): """ Get the definition for the property I{name}. @param name: The property I{name} to find the definition for. @type name: str @return: The property definition @rtype: L{Definition} @raise AttributeError: On not found. """ d = self.definitions.get(name) if d is None: raise AttributeError(name) return d def update(self, other): """ Update the property values as specified by keyword/value. @param other: An object to update from. @type other: (dict|L{Properties}) @return: self @rtype: L{Properties} """ if isinstance(other, Properties): other = other.defined for n,v in list(other.items()): self.set(n, v) return self def notset(self, name): """ Get whether a property has never been set by I{name}. @param name: A property name. @type name: str @return: True if never been set. @rtype: bool """ self.provider(name).__notset(name) def set(self, name, value): """ Set the I{value} of a property by I{name}. The value is validated against the definition and set to the default when I{value} is None. @param name: The property name. @type name: str @param value: The new property value. @type value: any @return: self @rtype: L{Properties} """ self.provider(name).__set(name, value) return self def unset(self, name): """ Unset a property by I{name}. @param name: A property name. @type name: str @return: self @rtype: L{Properties} """ self.provider(name).__set(name, None) return self def get(self, name, *df): """ Get the value of a property by I{name}. @param name: The property name. @type name: str @param df: An optional value to be returned when the value is not set @type df: [1]. @return: The stored value, or I{df[0]} if not set. @rtype: any """ return self.provider(name).__get(name, *df) def link(self, other): """ Link (associate) this object with anI{other} properties object to create a network of properties. Links are bidirectional. @param other: The object to link. @type other: L{Properties} @return: self @rtype: L{Properties} """ Link(self, other) return self def unlink(self, *others): """ Unlink (disassociate) the specified properties object. @param others: The list object to unlink. Unspecified means unlink all. @type others: [L{Properties},..] @return: self @rtype: L{Properties} """ if not len(others): others = self.links[:] for p in self.links[:]: if p in others: p.teardown() return self def provider(self, name, history=None): """ Find the provider of the property by I{name}. @param name: The property name. @type name: str @param history: A history of nodes checked to prevent circular hunting. @type history: [L{Properties},..] @return: The provider when found. Otherwise, None (when nested) and I{self} when not nested. @rtype: L{Properties} """ if history is None: history = [] history.append(self) if name in self.definitions: return self for x in self.links: if x in history: continue provider = x.provider(name, history) if provider is not None: return provider history.remove(self) if len(history): return None return self def keys(self, history=None): """ Get the set of I{all} property names. @param history: A history of nodes checked to prevent circular hunting. @type history: [L{Properties},..] @return: A set of property names. @rtype: list """ if history is None: history = [] history.append(self) keys = set() keys.update(list(self.definitions.keys())) for x in self.links: if x in history: continue keys.update(x.keys(history)) history.remove(self) return keys def domains(self, history=None): """ Get the set of I{all} domain names. @param history: A history of nodes checked to prevent circular hunting. @type history: [L{Properties},..] @return: A set of domain names. @rtype: list """ if history is None: history = [] history.append(self) domains = set() domains.add(self.domain) for x in self.links: if x in history: continue domains.update(x.domains(history)) history.remove(self) return domains def prime(self): """ Prime the stored values based on default values found in property definitions. @return: self @rtype: L{Properties} """ for d in list(self.definitions.values()): self.defined[d.name] = d.default return self def __notset(self, name): return not (name in self.modified) def __set(self, name, value): d = self.definition(name) d.validate(value) value = d.nvl(value) prev = self.defined[name] self.defined[name] = value self.modified.add(name) d.linker.updated(self, prev, value) def __get(self, name, *df): d = self.definition(name) value = self.defined.get(name) if value == d.default and len(df): value = df[0] return value def str(self, history): s = [] s.append('Definitions:') for d in list(self.definitions.values()): s.append('\t%s' % repr(d)) s.append('Content:') for d in list(self.defined.items()): s.append('\t%s' % str(d)) if self not in history: history.append(self) s.append('Linked:') for x in self.links: s.append(x.str(history)) history.remove(self) return '\n'.join(s) def __repr__(self): return str(self) def __str__(self): return self.str([]) class Skin(object): """ The meta-programming I{skin} around the L{Properties} object. @ivar __pts__: The wrapped object. @type __pts__: L{Properties}. """ def __init__(self, domain, definitions, kwargs): self.__pts__ = Properties(domain, definitions, kwargs) def __setattr__(self, name, value): builtin = name.startswith('__') and name.endswith('__') if builtin: self.__dict__[name] = value return self.__pts__.set(name, value) def __getattr__(self, name): return self.__pts__.get(name) def __repr__(self): return str(self) def __str__(self): return str(self.__pts__) class Unskin(object): def __new__(self, *args, **kwargs): return args[0].__pts__ class Inspector: """ Wrapper inspector. """ def __init__(self, options): self.properties = options.__pts__ def get(self, name, *df): """ Get the value of a property by I{name}. @param name: The property name. @type name: str @param df: An optional value to be returned when the value is not set @type df: [1]. @return: The stored value, or I{df[0]} if not set. @rtype: any """ return self.properties.get(name, *df) def update(self, **kwargs): """ Update the property values as specified by keyword/value. @param kwargs: A list of property name/values to set. @type kwargs: dict @return: self @rtype: L{Properties} """ return self.properties.update(**kwargs) def link(self, other): """ Link (associate) this object with anI{other} properties object to create a network of properties. Links are bidirectional. @param other: The object to link. @type other: L{Properties} @return: self @rtype: L{Properties} """ p = other.__pts__ return self.properties.link(p) def unlink(self, other): """ Unlink (disassociate) the specified properties object. @param other: The object to unlink. @type other: L{Properties} @return: self @rtype: L{Properties} """ p = other.__pts__ return self.properties.unlink(p) suds-1.1.2/suds/reader.py000066400000000000000000000141341425611400200152610ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ XML document reader classes providing integration with the suds library's caching system. """ import suds.cache import suds.plugin import suds.sax.parser import suds.transport try: from hashlib import md5 except ImportError: # 'hashlib' package added in Python 2.5 so use the now deprecated/removed # 'md5' package in older Python versions. from md5 import md5 class Reader(object): """ Provides integration with the cache. @ivar options: An options object. @type options: I{Options} """ def __init__(self, options): """ @param options: An options object. @type options: I{Options} """ self.options = options self.plugins = suds.plugin.PluginContainer(options.plugins) def mangle(self, name, x): """ Mangle the name by hashing the I{name} and appending I{x}. @return: The mangled name. @rtype: str """ try: # FIPS requires usedforsecurity=False and might not be # available on all distros: https://bugs.python.org/issue9216 h = md5(name.encode(), usedforsecurity=False).hexdigest() except (AttributeError, TypeError): h = md5(name.encode()).hexdigest() return '%s-%s' % (h, x) class DefinitionsReader(Reader): """ Integrates between the WSDL Definitions object and the object cache. @ivar fn: A factory function used to create objects not found in the cache. @type fn: I{Constructor} """ def __init__(self, options, fn): """ @param options: An options object. @type options: I{Options} @param fn: A factory function used to create objects not found in the cache. @type fn: I{Constructor} """ super(DefinitionsReader, self).__init__(options) self.fn = fn def open(self, url): """ Open a WSDL schema at the specified I{URL}. First, the WSDL schema is looked up in the I{object cache}. If not found, a new one constructed using the I{fn} factory function and the result is cached for the next open(). @param url: A WSDL URL. @type url: str. @return: The WSDL object. @rtype: I{Definitions} """ cache = self.__cache() id = self.mangle(url, "wsdl") wsdl = cache.get(id) if wsdl is None: wsdl = self.fn(url, self.options) cache.put(id, wsdl) else: # Cached WSDL Definitions objects may have been created with # different options so we update them here with our current ones. wsdl.options = self.options for imp in wsdl.imports: imp.imported.options = self.options return wsdl def __cache(self): """ Get the I{object cache}. @return: The I{cache} when I{cachingpolicy} = B{1}. @rtype: L{Cache} """ if self.options.cachingpolicy == 1: return self.options.cache return suds.cache.NoCache() class DocumentReader(Reader): """Integrates between the SAX L{Parser} and the document cache.""" def open(self, url): """ Open an XML document at the specified I{URL}. First, a preparsed document is looked up in the I{object cache}. If not found, its content is fetched from an external source and parsed using the SAX parser. The result is cached for the next open(). @param url: A document URL. @type url: str. @return: The specified XML document. @rtype: I{Document} """ cache = self.__cache() id = self.mangle(url, "document") xml = cache.get(id) if xml is None: xml = self.__fetch(url) cache.put(id, xml) self.plugins.document.parsed(url=url, document=xml.root()) return xml def __cache(self): """ Get the I{object cache}. @return: The I{cache} when I{cachingpolicy} = B{0}. @rtype: L{Cache} """ if self.options.cachingpolicy == 0: return self.options.cache return suds.cache.NoCache() def __fetch(self, url): """ Fetch document content from an external source. The document content will first be looked up in the registered document store, and if not found there, downloaded using the registered transport system. Before being returned, the fetched document content first gets processed by all the registered 'loaded' plugins. @param url: A document URL. @type url: str. @return: A file pointer to the fetched document content. @rtype: file-like """ content = None store = self.options.documentStore if store is not None: content = store.open(url) if content is None: request = suds.transport.Request(url) request.headers = self.options.headers fp = self.options.transport.open(request) try: content = fp.read() finally: fp.close() ctx = self.plugins.document.loaded(url=url, document=content) content = ctx.document sax = suds.sax.parser.Parser() return sax.parse(string=content) suds-1.1.2/suds/resolver.py000066400000000000000000000362741425611400200156710ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ The I{resolver} module provides a collection of classes that provide wsdl/xsd named type resolution. """ from suds import * from suds.sax import splitPrefix, Namespace from suds.sudsobject import Object from suds.xsd.query import BlindQuery, TypeQuery, qualify import re from logging import getLogger log = getLogger(__name__) class Resolver: """ An I{abstract} schema-type resolver. @ivar schema: A schema object. @type schema: L{xsd.schema.Schema} """ def __init__(self, schema): """ @param schema: A schema object. @type schema: L{xsd.schema.Schema} """ self.schema = schema def find(self, name, resolved=True): """ Get the definition object for the schema object by name. @param name: The name of a schema object. @type name: basestring @param resolved: A flag indicating that the fully resolved type should be returned. @type resolved: boolean @return: The found schema I{type} @rtype: L{xsd.sxbase.SchemaObject} """ log.debug('searching schema for (%s)', name) qref = qualify(name, self.schema.root, self.schema.tns) query = BlindQuery(qref) result = query.execute(self.schema) if result is None: log.error('(%s) not-found', name) return None log.debug('found (%s) as (%s)', name, Repr(result)) if resolved: result = result.resolve() return result class PathResolver(Resolver): """ Resolves the definition object for the schema type located at a given path. The path may contain (.) dot notation to specify nested types. @ivar wsdl: A wsdl object. @type wsdl: L{wsdl.Definitions} """ def __init__(self, wsdl, ps='.'): """ @param wsdl: A schema object. @type wsdl: L{wsdl.Definitions} @param ps: The path separator character @type ps: char """ Resolver.__init__(self, wsdl.schema) self.wsdl = wsdl self.altp = re.compile('({)(.+)(})(.+)') self.splitp = re.compile('({.+})*[^\\%s]+' % ps[0]) def find(self, path, resolved=True): """ Get the definition object for the schema type located at the specified path. The path may contain (.) dot notation to specify nested types. Actually, the path separator is usually a (.) but can be redefined during contruction. @param path: A (.) separated path to a schema type. @type path: basestring @param resolved: A flag indicating that the fully resolved type should be returned. @type resolved: boolean @return: The found schema I{type} @rtype: L{xsd.sxbase.SchemaObject} """ result = None parts = self.split(path) try: result = self.root(parts) if len(parts) > 1: result = result.resolve(nobuiltin=True) result = self.branch(result, parts) result = self.leaf(result, parts) if resolved: result = result.resolve(nobuiltin=True) except PathResolver.BadPath: log.error('path: "%s", not-found' % path) return result def root(self, parts): """ Find the path root. @param parts: A list of path parts. @type parts: [str,..] @return: The root. @rtype: L{xsd.sxbase.SchemaObject} """ result = None name = parts[0] log.debug('searching schema for (%s)', name) qref = self.qualify(parts[0]) query = BlindQuery(qref) result = query.execute(self.schema) if result is None: log.error('(%s) not-found', name) raise PathResolver.BadPath(name) log.debug('found (%s) as (%s)', name, Repr(result)) return result def branch(self, root, parts): """ Traverse the path until a leaf is reached. @param parts: A list of path parts. @type parts: [str,..] @param root: The root. @type root: L{xsd.sxbase.SchemaObject} @return: The end of the branch. @rtype: L{xsd.sxbase.SchemaObject} """ result = root for part in parts[1:-1]: name = splitPrefix(part)[1] log.debug('searching parent (%s) for (%s)', Repr(result), name) result, ancestry = result.get_child(name) if result is None: log.error('(%s) not-found', name) raise PathResolver.BadPath(name) result = result.resolve(nobuiltin=True) log.debug('found (%s) as (%s)', name, Repr(result)) return result def leaf(self, parent, parts): """ Find the leaf. @param parts: A list of path parts. @type parts: [str,..] @param parent: The leaf's parent. @type parent: L{xsd.sxbase.SchemaObject} @return: The leaf. @rtype: L{xsd.sxbase.SchemaObject} """ name = splitPrefix(parts[-1])[1] if name.startswith('@'): result, path = parent.get_attribute(name[1:]) else: result, ancestry = parent.get_child(name) if result is None: raise PathResolver.BadPath(name) return result def qualify(self, name): """ Qualify the name as either: - plain name - ns prefixed name (eg: ns0:Person) - fully ns qualified name (eg: {http://myns-uri}Person) @param name: The name of an object in the schema. @type name: str @return: A qualified name. @rtype: qname """ m = self.altp.match(name) if m is None: return qualify(name, self.wsdl.root, self.wsdl.tns) else: return (m.group(4), m.group(2)) def split(self, s): """ Split the string on (.) while preserving any (.) inside the '{}' alternalte syntax for full ns qualification. @param s: A plain or qualified name. @type s: str @return: A list of the name's parts. @rtype: [str,..] """ parts = [] b = 0 while 1: m = self.splitp.match(s, b) if m is None: break b,e = m.span() parts.append(s[b:e]) b = e+1 return parts class BadPath(Exception): pass class TreeResolver(Resolver): """ The tree resolver is a I{stateful} tree resolver used to resolve each node in a tree. As such, it mirrors the tree structure to ensure that nodes are resolved in context. @ivar stack: The context stack. @type stack: list """ def __init__(self, schema): """ @param schema: A schema object. @type schema: L{xsd.schema.Schema} """ Resolver.__init__(self, schema) self.stack = Stack() def reset(self): """ Reset the resolver's state. """ self.stack = Stack() def push(self, x): """ Push an I{object} onto the stack. @param x: An object to push. @type x: L{Frame} @return: The pushed frame. @rtype: L{Frame} """ if isinstance(x, Frame): frame = x else: frame = Frame(x) self.stack.append(frame) log.debug('push: (%s)\n%s', Repr(frame), Repr(self.stack)) return frame def top(self): """ Get the I{frame} at the top of the stack. @return: The top I{frame}, else Frame.Empty. @rtype: L{Frame} """ if len(self.stack): return self.stack[-1] else: return Frame.Empty() def pop(self): """ Pop the frame at the top of the stack. @return: The popped frame, else None. @rtype: L{Frame} """ if len(self.stack): popped = self.stack.pop() log.debug('pop: (%s)\n%s', Repr(popped), Repr(self.stack)) return popped log.debug('stack empty, not-popped') return None def depth(self): """ Get the current stack depth. @return: The current stack depth. @rtype: int """ return len(self.stack) def getchild(self, name, parent): """Get a child by name.""" log.debug('searching parent (%s) for (%s)', Repr(parent), name) if name.startswith('@'): return parent.get_attribute(name[1:]) return parent.get_child(name) class NodeResolver(TreeResolver): """ The node resolver is a I{stateful} XML document resolver used to resolve each node in a tree. As such, it mirrors the tree structure to ensure that nodes are resolved in context. """ def __init__(self, schema): """ @param schema: A schema object. @type schema: L{xsd.schema.Schema} """ TreeResolver.__init__(self, schema) def find(self, node, resolved=False, push=True): """ @param node: An xml node to be resolved. @type node: L{sax.element.Element} @param resolved: A flag indicating that the fully resolved type should be returned. @type resolved: boolean @param push: Indicates that the resolved type should be pushed onto the stack. @type push: boolean @return: The found schema I{type} @rtype: L{xsd.sxbase.SchemaObject} """ name = node.name parent = self.top().resolved if parent is None: result, ancestry = self.query(name, node) else: result, ancestry = self.getchild(name, parent) known = self.known(node) if result is None: return result if push: frame = Frame(result, resolved=known, ancestry=ancestry) pushed = self.push(frame) if resolved: result = result.resolve() return result def findattr(self, name, resolved=True): """ Find an attribute type definition. @param name: An attribute name. @type name: basestring @param resolved: A flag indicating that the fully resolved type should be returned. @type resolved: boolean @return: The found schema I{type} @rtype: L{xsd.sxbase.SchemaObject} """ name = '@%s'%name parent = self.top().resolved if parent is None: return None else: result, ancestry = self.getchild(name, parent) if result is None: return result if resolved: result = result.resolve() return result def query(self, name, node): """Blindly query the schema by name.""" log.debug('searching schema for (%s)', name) qref = qualify(name, node, node.namespace()) query = BlindQuery(qref) result = query.execute(self.schema) return (result, []) def known(self, node): """Resolve type referenced by @xsi:type.""" ref = node.get('type', Namespace.xsins) if ref is None: return None qref = qualify(ref, node, node.namespace()) query = BlindQuery(qref) return query.execute(self.schema) class GraphResolver(TreeResolver): """ The graph resolver is a I{stateful} L{Object} graph resolver used to resolve each node in a tree. As such, it mirrors the tree structure to ensure that nodes are resolved in context. """ def __init__(self, schema): """ @param schema: A schema object. @type schema: L{xsd.schema.Schema} """ TreeResolver.__init__(self, schema) def find(self, name, object, resolved=False, push=True): """ @param name: The name of the object to be resolved. @type name: basestring @param object: The name's value. @type object: (any|L{Object}) @param resolved: A flag indicating that the fully resolved type should be returned. @type resolved: boolean @param push: Indicates that the resolved type should be pushed onto the stack. @type push: boolean @return: The found schema I{type} @rtype: L{xsd.sxbase.SchemaObject} """ known = None parent = self.top().resolved if parent is None: result, ancestry = self.query(name) else: result, ancestry = self.getchild(name, parent) if result is None: return None if isinstance(object, Object): known = self.known(object) if push: frame = Frame(result, resolved=known, ancestry=ancestry) pushed = self.push(frame) if resolved: if known is None: result = result.resolve() else: result = known return result def query(self, name): """Blindly query the schema by name.""" log.debug('searching schema for (%s)', name) schema = self.schema wsdl = self.wsdl() if wsdl is None: qref = qualify(name, schema.root, schema.tns) else: qref = qualify(name, wsdl.root, wsdl.tns) query = BlindQuery(qref) result = query.execute(schema) return (result, []) def wsdl(self): """Get the wsdl.""" container = self.schema.container if container is None: return None else: return container.wsdl def known(self, object): """Get the type specified in the object's metadata.""" try: md = object.__metadata__ known = md.sxtype return known except Exception: pass class Frame: def __init__(self, type, resolved=None, ancestry=()): self.type = type if resolved is None: resolved = type.resolve() self.resolved = resolved.resolve() self.ancestry = ancestry def __str__(self): return '%s\n%s\n%s' % \ (Repr(self.type), Repr(self.resolved), [Repr(t) for t in self.ancestry]) class Empty: def __getattr__(self, name): if name == 'ancestry': return () else: return None class Stack(list): def __repr__(self): result = [] for item in self: result.append(repr(item)) return '\n'.join(result) suds-1.1.2/suds/sax/000077500000000000000000000000001425611400200142355ustar00rootroot00000000000000suds-1.1.2/suds/sax/__init__.py000066400000000000000000000061411425611400200163500ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ The sax module contains a collection of classes that provide a (D)ocument (O)bject (M)odel representation of an XML document. The goal is to provide an easy, intuitive interface for managing XML documents. Although the term DOM is used here, this model is B{far} better. XML namespaces in suds are represented using a (2) element tuple containing the prefix and the URI, e.g. I{('tns', 'http://myns')} @var encoder: A I{pluggable} XML special character processor used to encode/ decode strings. @type encoder: L{Encoder} """ from suds.sax.enc import Encoder # pluggable XML special character encoder. encoder = Encoder() def splitPrefix(name): """ Split the name into a tuple (I{prefix}, I{name}). The first element in the tuple is I{None} when the name does not have a prefix. @param name: A node name containing an optional prefix. @type name: basestring @return: A tuple containing the (2) parts of I{name}. @rtype: (I{prefix}, I{name}) """ if isinstance(name, str) and ":" in name: return tuple(name.split(":", 1)) return None, name class Namespace: """XML namespace.""" default = (None, None) xmlns = ("xml", "http://www.w3.org/XML/1998/namespace") xsdns = ("xs", "http://www.w3.org/2001/XMLSchema") xsins = ("xsi", "http://www.w3.org/2001/XMLSchema-instance") all = (xsdns, xsins) @classmethod def create(cls, p=None, u=None): return p, u @classmethod def none(cls, ns): return ns == cls.default @classmethod def xsd(cls, ns): try: return cls.w3(ns) and ns[1].endswith("XMLSchema") except Exception: pass return False @classmethod def xsi(cls, ns): try: return cls.w3(ns) and ns[1].endswith("XMLSchema-instance") except Exception: pass return False @classmethod def xs(cls, ns): return cls.xsd(ns) or cls.xsi(ns) @classmethod def w3(cls, ns): try: return ns[1].startswith("http://www.w3.org") except Exception: pass return False @classmethod def isns(cls, ns): try: return isinstance(ns, tuple) and len(ns) == len(cls.default) except Exception: pass return False suds-1.1.2/suds/sax/attribute.py000066400000000000000000000120221425611400200166070ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Provides XML I{attribute} classes. """ from suds import UnicodeMixin from suds.sax import splitPrefix, Namespace from suds.sax.text import Text class Attribute(UnicodeMixin): """ An XML attribute object. @ivar parent: The node containing this attribute. @type parent: L{element.Element} @ivar prefix: The I{optional} namespace prefix. @type prefix: basestring @ivar name: The I{unqualified} attribute name. @type name: basestring @ivar value: The attribute's value. @type value: basestring """ def __init__(self, name, value=None): """ @param name: The attribute's name with I{optional} namespace prefix. @type name: basestring @param value: The attribute's value. @type value: basestring """ self.parent = None self.prefix, self.name = splitPrefix(name) self.setValue(value) def clone(self, parent=None): """ Clone this object. @param parent: The parent for the clone. @type parent: L{element.Element} @return: A copy of this object assigned to the new parent. @rtype: L{Attribute} """ a = Attribute(self.qname(), self.value) a.parent = parent return a def qname(self): """ Get this attribute's B{fully} qualified name. @return: The fully qualified name. @rtype: basestring """ if self.prefix is None: return self.name return ":".join((self.prefix, self.name)) def setValue(self, value): """ Set the attribute's value. @param value: The new value (may be None). @type value: basestring @return: self @rtype: L{Attribute} """ if isinstance(value, Text): self.value = value else: self.value = Text(value) return self def getValue(self, default=Text("")): """ Get the attributes value with optional default. @param default: An optional value to return when the attribute's value has not been set. @type default: basestring @return: The attribute's value, or I{default}. @rtype: L{Text} """ return self.value or default def hasText(self): """ Get whether the attribute has a non-empty I{text} string value. @return: True when has I{text}. @rtype: boolean """ return bool(self.value) def namespace(self): """ Get the attribute's namespace. This may either be the namespace defined by an optional prefix, or the default namespace. @return: The attribute's namespace. @rtype: (I{prefix}, I{name}) """ if self.prefix is None: return Namespace.default return self.resolvePrefix(self.prefix) def resolvePrefix(self, prefix): """ Resolve the specified prefix to a known namespace. @param prefix: A declared prefix. @type prefix: basestring @return: The namespace mapped to I{prefix}. @rtype: (I{prefix}, I{name}) """ if self.parent is None: return Namespace.default return self.parent.resolvePrefix(prefix) def match(self, name=None, ns=None): """ Match by (optional) name and/or (optional) namespace. @param name: The optional attribute tag name. @type name: str @param ns: An optional namespace. @type ns: (I{prefix}, I{name}) @return: True if matched. @rtype: boolean """ byname = name is None or (self.name == name) byns = ns is None or (self.namespace()[1] == ns[1]) return byname and byns def __eq__(self, rhs): """Equals operator.""" return (isinstance(rhs, Attribute) and self.prefix == rhs.name and self.name == rhs.name) def __repr__(self): """Programmer friendly string representation.""" return "attr (prefix=%s, name=%s, value=(%s))" % (self.prefix, self.name, self.value) def __unicode__(self): """XML string representation.""" return '%s="%s"' % (self.qname(), self.value and self.value.escape()) suds-1.1.2/suds/sax/date.py000066400000000000000000000332611425611400200155310ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) # based on code by: Glen Walker # based on code by: Nathan Van Gheem ( vangheem@gmail.com ) """Classes for conversion between XML dates and Python objects.""" from suds import UnicodeMixin import datetime import re import time _SNIPPET_DATE = \ r"(?P\d{1,})-(?P\d{1,2})-(?P\d{1,2})" _SNIPPET_TIME = \ r"(?P\d{1,2}):(?P[0-5]?[0-9]):(?P[0-5]?[0-9])" \ r"(?:\.(?P\d+))?" _SNIPPET_ZONE = \ r"(?:(?P[-+])(?P\d{1,2})" \ r"(?::(?P[0-5]?[0-9]))?)" \ r"|(?P[Zz])" _PATTERN_DATE = r"^%s(?:%s)?$" % (_SNIPPET_DATE, _SNIPPET_ZONE) _PATTERN_TIME = r"^%s(?:%s)?$" % (_SNIPPET_TIME, _SNIPPET_ZONE) _PATTERN_DATETIME = r"^%s[T ]%s(?:%s)?$" % (_SNIPPET_DATE, _SNIPPET_TIME, _SNIPPET_ZONE) _RE_DATE = re.compile(_PATTERN_DATE) _RE_TIME = re.compile(_PATTERN_TIME) _RE_DATETIME = re.compile(_PATTERN_DATETIME) class Date(UnicodeMixin): """ An XML date object supporting the xsd:date datatype. @ivar value: The object value. @type value: B{datetime}.I{date} """ def __init__(self, value): """ @param value: The date value of the object. @type value: (datetime.date|str) @raise ValueError: When I{value} is invalid. """ if isinstance(value, datetime.datetime): self.value = value.date() elif isinstance(value, datetime.date): self.value = value elif isinstance(value, str): self.value = self.__parse(value) else: raise ValueError("invalid type for Date(): %s" % type(value)) @staticmethod def __parse(value): """ Parse the string date. Supports the subset of ISO8601 used by xsd:date, but is lenient with what is accepted, handling most reasonable syntax. Any timezone is parsed but ignored because a) it is meaningless without a time and b) B{datetime}.I{date} does not support timezone information. @param value: A date string. @type value: str @return: A date object. @rtype: B{datetime}.I{date} """ match_result = _RE_DATE.match(value) if match_result is None: raise ValueError("date data has invalid format '%s'" % (value,)) return _date_from_match(match_result) def __unicode__(self): return self.value.isoformat() class DateTime(UnicodeMixin): """ An XML datetime object supporting the xsd:dateTime datatype. @ivar value: The object value. @type value: B{datetime}.I{datetime} """ def __init__(self, value): """ @param value: The datetime value of the object. @type value: (datetime.datetime|str) @raise ValueError: When I{value} is invalid. """ if isinstance(value, datetime.datetime): self.value = value elif isinstance(value, str): self.value = self.__parse(value) else: raise ValueError("invalid type for DateTime(): %s" % type(value)) @staticmethod def __parse(value): """ Parse the string datetime. Supports the subset of ISO8601 used by xsd:dateTime, but is lenient with what is accepted, handling most reasonable syntax. Subsecond information is rounded to microseconds due to a restriction in the python datetime.datetime/time implementation. @param value: A datetime string. @type value: str @return: A datetime object. @rtype: B{datetime}.I{datetime} """ match_result = _RE_DATETIME.match(value) if match_result is None: raise ValueError("date data has invalid format '%s'" % (value,)) date = _date_from_match(match_result) time, round_up = _time_from_match(match_result) tzinfo = _tzinfo_from_match(match_result) value = datetime.datetime.combine(date, time) value = value.replace(tzinfo=tzinfo) if round_up: value += datetime.timedelta(microseconds=1) return value def __unicode__(self): return self.value.isoformat() class Time(UnicodeMixin): """ An XML time object supporting the xsd:time datatype. @ivar value: The object value. @type value: B{datetime}.I{time} """ def __init__(self, value): """ @param value: The time value of the object. @type value: (datetime.time|str) @raise ValueError: When I{value} is invalid. """ if isinstance(value, datetime.time): self.value = value elif isinstance(value, str): self.value = self.__parse(value) else: raise ValueError("invalid type for Time(): %s" % type(value)) @staticmethod def __parse(value): """ Parse the string date. Supports the subset of ISO8601 used by xsd:time, but is lenient with what is accepted, handling most reasonable syntax. Subsecond information is rounded to microseconds due to a restriction in the python datetime.time implementation. @param value: A time string. @type value: str @return: A time object. @rtype: B{datetime}.I{time} """ match_result = _RE_TIME.match(value) if match_result is None: raise ValueError("date data has invalid format '%s'" % (value,)) time, round_up = _time_from_match(match_result) tzinfo = _tzinfo_from_match(match_result) if round_up: time = _bump_up_time_by_microsecond(time) return time.replace(tzinfo=tzinfo) def __unicode__(self): return self.value.isoformat() class FixedOffsetTimezone(datetime.tzinfo, UnicodeMixin): """ A timezone with a fixed offset and no daylight savings adjustment. http://docs.python.org/library/datetime.html#datetime.tzinfo """ def __init__(self, offset): """ @param offset: The fixed offset of the timezone. @type offset: I{int} or B{datetime}.I{timedelta} """ if type(offset) == int: offset = datetime.timedelta(hours=offset) elif type(offset) != datetime.timedelta: raise TypeError("timezone offset must be an int or " "datetime.timedelta") if offset.microseconds or (offset.seconds % 60 != 0): raise ValueError("timezone offset must have minute precision") self.__offset = offset def dst(self, dt): """ http://docs.python.org/library/datetime.html#datetime.tzinfo.dst """ return datetime.timedelta(0) def utcoffset(self, dt): """ http://docs.python.org/library/datetime.html#datetime.tzinfo.utcoffset """ return self.__offset def tzname(self, dt): """ http://docs.python.org/library/datetime.html#datetime.tzinfo.tzname """ # total_seconds was introduced in Python 2.7 if hasattr(self.__offset, "total_seconds"): total_seconds = self.__offset.total_seconds() else: total_seconds = (self.__offset.days * 24 * 60 * 60) + \ (self.__offset.seconds) hours = total_seconds // (60 * 60) total_seconds -= hours * 60 * 60 minutes = total_seconds // 60 total_seconds -= minutes * 60 seconds = total_seconds // 1 total_seconds -= seconds if seconds: return "%+03d:%02d:%02d" % (hours, minutes, seconds) return "%+03d:%02d" % (hours, minutes) def __unicode__(self): return "FixedOffsetTimezone %s" % (self.tzname(None),) class UtcTimezone(FixedOffsetTimezone): """ The UTC timezone. http://docs.python.org/library/datetime.html#datetime.tzinfo """ def __init__(self): FixedOffsetTimezone.__init__(self, datetime.timedelta(0)) def tzname(self, dt): """ http://docs.python.org/library/datetime.html#datetime.tzinfo.tzname """ return "UTC" def __unicode__(self): return "UtcTimezone" class LocalTimezone(datetime.tzinfo): """ The local timezone of the operating system. http://docs.python.org/library/datetime.html#datetime.tzinfo """ def __init__(self): self.__offset = datetime.timedelta(seconds=-time.timezone) self.__dst_offset = None if time.daylight: self.__dst_offset = datetime.timedelta(seconds=-time.altzone) def dst(self, dt): """ http://docs.python.org/library/datetime.html#datetime.tzinfo.dst """ if self.__is_daylight_time(dt): return self.__dst_offset - self.__offset return datetime.timedelta(0) def tzname(self, dt): """ http://docs.python.org/library/datetime.html#datetime.tzinfo.tzname """ if self.__is_daylight_time(dt): return time.tzname[1] return time.tzname[0] def utcoffset(self, dt): """ http://docs.python.org/library/datetime.html#datetime.tzinfo.utcoffset """ if self.__is_daylight_time(dt): return self.__dst_offset return self.__offset def __is_daylight_time(self, dt): if not time.daylight: return False time_tuple = dt.replace(tzinfo=None).timetuple() time_tuple = time.localtime(time.mktime(time_tuple)) return time_tuple.tm_isdst > 0 def __unicode__(self): dt = datetime.datetime.now() return "LocalTimezone %s offset: %s dst: %s" % (self.tzname(dt), self.utcoffset(dt), self.dst(dt)) def _bump_up_time_by_microsecond(time): """ Helper function bumping up the given datetime.time by a microsecond, cycling around silently to 00:00:00.0 in case of an overflow. @param time: Time object. @type time: B{datetime}.I{time} @return: Time object. @rtype: B{datetime}.I{time} """ dt = datetime.datetime(2000, 1, 1, time.hour, time.minute, time.second, time.microsecond) dt += datetime.timedelta(microseconds=1) return dt.time() def _date_from_match(match_object): """ Create a date object from a regular expression match. The regular expression match is expected to be from _RE_DATE or _RE_DATETIME. @param match_object: The regular expression match. @type match_object: B{re}.I{MatchObject} @return: A date object. @rtype: B{datetime}.I{date} """ year = int(match_object.group("year")) month = int(match_object.group("month")) day = int(match_object.group("day")) return datetime.date(year, month, day) def _time_from_match(match_object): """ Create a time object from a regular expression match. Returns the time object and information whether the resulting time should be bumped up by one microsecond due to microsecond rounding. Subsecond information is rounded to microseconds due to a restriction in the python datetime.datetime/time implementation. The regular expression match is expected to be from _RE_DATETIME or _RE_TIME. @param match_object: The regular expression match. @type match_object: B{re}.I{MatchObject} @return: Time object + rounding flag. @rtype: tuple of B{datetime}.I{time} and bool """ hour = int(match_object.group('hour')) minute = int(match_object.group('minute')) second = int(match_object.group('second')) subsecond = match_object.group('subsecond') round_up = False microsecond = 0 if subsecond: round_up = len(subsecond) > 6 and int(subsecond[6]) >= 5 subsecond = subsecond[:6] microsecond = int(subsecond + "0" * (6 - len(subsecond))) return datetime.time(hour, minute, second, microsecond), round_up def _tzinfo_from_match(match_object): """ Create a timezone information object from a regular expression match. The regular expression match is expected to be from _RE_DATE, _RE_DATETIME or _RE_TIME. @param match_object: The regular expression match. @type match_object: B{re}.I{MatchObject} @return: A timezone information object. @rtype: B{datetime}.I{tzinfo} """ tz_utc = match_object.group("tz_utc") if tz_utc: return UtcTimezone() tz_sign = match_object.group("tz_sign") if not tz_sign: return h = int(match_object.group("tz_hour") or 0) m = int(match_object.group("tz_minute") or 0) if h == 0 and m == 0: return UtcTimezone() # Python limitation - timezone offsets larger than one day (in absolute) # will cause operations depending on tzinfo.utcoffset() to fail, e.g. # comparing two timezone aware datetime.datetime/time objects. if h >= 24: raise ValueError("timezone indicator too large") tz_delta = datetime.timedelta(hours=h, minutes=m) if tz_sign == "-": tz_delta *= -1 return FixedOffsetTimezone(tz_delta) suds-1.1.2/suds/sax/document.py000066400000000000000000000127771425611400200164430ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Provides XML I{document} classes. """ from suds import * from suds.sax import * from suds.sax.element import Element class Document(UnicodeMixin): """ An XML Document """ DECL = '' def __init__(self, root=None): """ @param root: A root L{Element} or name used to build the document root element. @type root: (L{Element}|str|None) """ self.__root = None self.append(root) def root(self): """ Get the document root element (can be None) @return: The document root. @rtype: L{Element} """ return self.__root def append(self, node): """ Append (set) the document root. @param node: A root L{Element} or name used to build the document root element. @type node: (L{Element}|str|None) """ if isinstance(node, str): self.__root = Element(node) return if isinstance(node, Element): self.__root = node return def getChild(self, name, ns=None, default=None): """ Get a child by (optional) name and/or (optional) namespace. @param name: The name of a child element (may contain prefix). @type name: basestring @param ns: An optional namespace used to match the child. @type ns: (I{prefix}, I{name}) @param default: Returned when child not-found. @type default: L{Element} @return: The requested child, or I{default} when not-found. @rtype: L{Element} """ if self.__root is None: return default if ns is None: prefix, name = splitPrefix(name) if prefix is None: ns = None else: ns = self.__root.resolvePrefix(prefix) if self.__root.match(name, ns): return self.__root else: return default def childAtPath(self, path): """ Get a child at I{path} where I{path} is a (/) separated list of element names that are expected to be children. @param path: A (/) separated list of element names. @type path: basestring @return: The leaf node at the end of I{path} @rtype: L{Element} """ if self.__root is None: return None if path[0] == '/': path = path[1:] path = path.split('/',1) if self.getChild(path[0]) is None: return None if len(path) > 1: return self.__root.childAtPath(path[1]) else: return self.__root def childrenAtPath(self, path): """ Get a list of children at I{path} where I{path} is a (/) separated list of element names that are expected to be children. @param path: A (/) separated list of element names. @type path: basestring @return: The collection leaf nodes at the end of I{path} @rtype: [L{Element},...] """ if self.__root is None: return [] if path[0] == '/': path = path[1:] path = path.split('/',1) if self.getChild(path[0]) is None: return [] if len(path) > 1: return self.__root.childrenAtPath(path[1]) else: return [self.__root,] def getChildren(self, name=None, ns=None): """ Get a list of children by (optional) name and/or (optional) namespace. @param name: The name of a child element (may contain prefix). @type name: basestring @param ns: An optional namespace used to match the child. @type ns: (I{prefix}, I{name}) @return: The list of matching children. @rtype: [L{Element},...] """ if name is None: matched = self.__root else: matched = self.getChild(name, ns) if matched is None: return [] else: return [matched,] def str(self): """ Get a string representation of this XML document. @return: A I{pretty} string. @rtype: basestring """ s = [] s.append(self.DECL) root = self.root() if root is not None: s.append('\n') s.append(root.str()) return ''.join(s) def plain(self): """ Get a string representation of this XML document. @return: A I{plain} string. @rtype: basestring """ s = [] s.append(self.DECL) root = self.root() if root is not None: s.append(root.plain()) return ''.join(s) def __unicode__(self): return self.str() suds-1.1.2/suds/sax/element.py000066400000000000000000001036711425611400200162500ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ XML I{element} classes. """ from suds import * from suds.sax import * from suds.sax.text import Text from suds.sax.attribute import Attribute class Element(UnicodeMixin): """ An XML element object. @ivar parent: The node containing this attribute. @type parent: L{Element} @ivar prefix: The I{optional} namespace prefix. @type prefix: basestring @ivar name: The I{unqualified} name of the attribute. @type name: basestring @ivar expns: An explicit namespace (xmlns="..."). @type expns: (I{prefix}, I{name}) @ivar nsprefixes: A mapping of prefixes to namespaces. @type nsprefixes: dict @ivar attributes: A list of XML attributes. @type attributes: [I{Attribute},...] @ivar text: The element's I{text} content. @type text: basestring @ivar children: A list of child elements. @type children: [I{Element},...] @cvar matcher: A collection of I{lambda} for string matching. @cvar specialprefixes: A dictionary of builtin-special prefixes. """ matcher = { "eq": lambda a, b: a == b, "startswith": lambda a, b: a.startswith(b), "endswith": lambda a, b: a.endswith(b), "contains": lambda a, b: b in a} specialprefixes = {Namespace.xmlns[0]: Namespace.xmlns[1]} @classmethod def buildPath(self, parent, path): """ Build the specified path as a/b/c. Any missing intermediate nodes are built automatically. @param parent: A parent element on which the path is built. @type parent: I{Element} @param path: A simple path separated by (/). @type path: basestring @return: The leaf node of I{path}. @rtype: L{Element} """ for tag in path.split("/"): child = parent.getChild(tag) if child is None: child = Element(tag, parent) parent = child return child def __init__(self, name, parent=None, ns=None): """ @param name: The element's (tag) name. May contain a prefix. @type name: basestring @param parent: An optional parent element. @type parent: I{Element} @param ns: An optional namespace. @type ns: (I{prefix}, I{name}) """ self.rename(name) self.expns = None self.nsprefixes = {} self.attributes = [] self.text = None if parent is not None and not isinstance(parent, Element): raise Exception("parent (%s) not-valid" % (parent.__class__.__name__,)) self.parent = parent self.children = [] self.applyns(ns) def rename(self, name): """ Rename the element. @param name: A new name for the element. @type name: basestring """ if name is None: raise Exception("name (%s) not-valid" % (name,)) self.prefix, self.name = splitPrefix(name) def setPrefix(self, p, u=None): """ Set the element namespace prefix. @param p: A new prefix for the element. @type p: basestring @param u: A namespace URI to be mapped to the prefix. @type u: basestring @return: self @rtype: L{Element} """ self.prefix = p if p is not None and u is not None: self.expns = None self.addPrefix(p, u) return self def qname(self): """ Get this element's B{fully} qualified name. @return: The fully qualified name. @rtype: basestring """ if self.prefix is None: return self.name return "%s:%s" % (self.prefix, self.name) def getRoot(self): """ Get the root (top) node of the tree. @return: The I{top} node of this tree. @rtype: I{Element} """ if self.parent is None: return self return self.parent.getRoot() def clone(self, parent=None): """ Deep clone of this element and children. @param parent: An optional parent for the copied fragment. @type parent: I{Element} @return: A deep copy parented by I{parent} @rtype: I{Element} """ root = Element(self.qname(), parent, self.namespace()) for a in self.attributes: root.append(a.clone(self)) for c in self.children: root.append(c.clone(self)) for ns in list(self.nsprefixes.items()): root.addPrefix(ns[0], ns[1]) return root def detach(self): """ Detach from parent. @return: This element removed from its parent's child list and I{parent}=I{None}. @rtype: L{Element} """ if self.parent is not None: if self in self.parent.children: self.parent.children.remove(self) self.parent = None return self def set(self, name, value): """ Set an attribute's value. @param name: The name of the attribute. @type name: basestring @param value: The attribute value. @type value: basestring @see: __setitem__() """ attr = self.getAttribute(name) if attr is None: attr = Attribute(name, value) self.append(attr) else: attr.setValue(value) def unset(self, name): """ Unset (remove) an attribute. @param name: The attribute name. @type name: str @return: self @rtype: L{Element} """ try: attr = self.getAttribute(name) self.attributes.remove(attr) except Exception: pass return self def get(self, name, ns=None, default=None): """ Get the value of an attribute by name. @param name: The name of the attribute. @type name: basestring @param ns: The optional attribute's namespace. @type ns: (I{prefix}, I{name}) @param default: An optional value to be returned when either the attribute does not exist or has no value. @type default: basestring @return: The attribute's value or I{default}. @rtype: basestring @see: __getitem__() """ attr = self.getAttribute(name, ns) if attr is None or attr.value is None: return default return attr.getValue() def setText(self, value): """ Set the element's L{Text} content. @param value: The element's text value. @type value: basestring @return: self @rtype: I{Element} """ if not isinstance(value, Text): value = Text(value) self.text = value return self def getText(self, default=None): """ Get the element's L{Text} content with optional default. @param default: A value to be returned when no text content exists. @type default: basestring @return: The text content, or I{default}. @rtype: L{Text} """ if self.hasText(): return self.text return default def trim(self): """ Trim leading and trailing whitespace. @return: self @rtype: L{Element} """ if self.hasText(): self.text = self.text.trim() return self def hasText(self): """ Get whether the element has non-empty I{text} string. @return: True when has I{text}. @rtype: boolean """ return bool(self.text) def namespace(self): """ Get the element's namespace. @return: The element's namespace by resolving the prefix, the explicit namespace or the inherited namespace. @rtype: (I{prefix}, I{name}) """ if self.prefix is None: return self.defaultNamespace() return self.resolvePrefix(self.prefix) def defaultNamespace(self): """ Get the default (unqualified namespace). This is the expns of the first node (looking up the tree) that has it set. @return: The namespace of a node when not qualified. @rtype: (I{prefix}, I{name}) """ p = self while p is not None: if p.expns is not None: return None, p.expns p = p.parent return Namespace.default def append(self, objects): """ Append the specified child based on whether it is an element or an attribute. @param objects: A (single|collection) of attribute(s) or element(s) to be added as children. @type objects: (L{Element}|L{Attribute}) @return: self @rtype: L{Element} """ if not isinstance(objects, (list, tuple)): objects = (objects,) for child in objects: if isinstance(child, Element): self.children.append(child) child.parent = self continue if isinstance(child, Attribute): self.attributes.append(child) child.parent = self continue raise Exception("append %s not-valid" % (child.__class__.__name__,)) return self def insert(self, objects, index=0): """ Insert an L{Element} content at the specified index. @param objects: A (single|collection) of attribute(s) or element(s) to be added as children. @type objects: (L{Element}|L{Attribute}) @param index: The position in the list of children to insert. @type index: int @return: self @rtype: L{Element} """ objects = (objects,) for child in objects: if not isinstance(child, Element): raise Exception("append %s not-valid" % (child.__class__.__name__,)) self.children.insert(index, child) child.parent = self return self def remove(self, child): """ Remove the specified child element or attribute. @param child: A child to remove. @type child: L{Element}|L{Attribute} @return: The detached I{child} when I{child} is an element, else None. @rtype: L{Element}|None """ if isinstance(child, Element): return child.detach() if isinstance(child, Attribute): self.attributes.remove(child) def replaceChild(self, child, content): """ Replace I{child} with the specified I{content}. @param child: A child element. @type child: L{Element} @param content: An element or collection of elements. @type content: L{Element} or [L{Element},...] """ if child not in self.children: raise Exception("child not-found") index = self.children.index(child) self.remove(child) if not isinstance(content, (list, tuple)): content = (content,) for node in content: self.children.insert(index, node.detach()) node.parent = self index += 1 def getAttribute(self, name, ns=None, default=None): """ Get an attribute by name and (optional) namespace. @param name: The name of a contained attribute (may contain prefix). @type name: basestring @param ns: An optional namespace @type ns: (I{prefix}, I{name}) @param default: Returned when attribute not-found. @type default: L{Attribute} @return: The requested attribute object. @rtype: L{Attribute} """ if ns is None: prefix, name = splitPrefix(name) if prefix is not None: ns = self.resolvePrefix(prefix) for a in self.attributes: if a.match(name, ns): return a return default def getChild(self, name, ns=None, default=None): """ Get a child by (optional) name and/or (optional) namespace. @param name: The name of a child element (may contain prefix). @type name: basestring @param ns: An optional namespace used to match the child. @type ns: (I{prefix}, I{name}) @param default: Returned when child not-found. @type default: L{Element} @return: The requested child, or I{default} when not-found. @rtype: L{Element} """ if ns is None: prefix, name = splitPrefix(name) if prefix is not None: ns = self.resolvePrefix(prefix) for c in self.children: if c.match(name, ns): return c return default def childAtPath(self, path): """ Get a child at I{path} where I{path} is a (/) separated list of element names that are expected to be children. @param path: A (/) separated list of element names. @type path: basestring @return: The leaf node at the end of I{path}. @rtype: L{Element} """ result = None node = self for name in path.split("/"): if not name: continue ns = None prefix, name = splitPrefix(name) if prefix is not None: ns = node.resolvePrefix(prefix) result = node.getChild(name, ns) if result is None: return node = result return result def childrenAtPath(self, path): """ Get a list of children at I{path} where I{path} is a (/) separated list of element names expected to be children. @param path: A (/) separated list of element names. @type path: basestring @return: The collection leaf nodes at the end of I{path}. @rtype: [L{Element},...] """ parts = [p for p in path.split("/") if p] if len(parts) == 1: return self.getChildren(path) return self.__childrenAtPath(parts) def getChildren(self, name=None, ns=None): """ Get a list of children by (optional) name and/or (optional) namespace. @param name: The name of a child element (may contain a prefix). @type name: basestring @param ns: An optional namespace used to match the child. @type ns: (I{prefix}, I{name}) @return: The list of matching children. @rtype: [L{Element},...] """ if ns is None: if name is None: return self.children prefix, name = splitPrefix(name) if prefix is not None: ns = self.resolvePrefix(prefix) return [c for c in self.children if c.match(name, ns)] def detachChildren(self): """ Detach and return this element's children. @return: The element's children (detached). @rtype: [L{Element},...] """ detached = self.children self.children = [] for child in detached: child.parent = None return detached def resolvePrefix(self, prefix, default=Namespace.default): """ Resolve the specified prefix to a namespace. The I{nsprefixes} is searched. If not found, walk up the tree until either resolved or the top of the tree is reached. Searching up the tree provides for inherited mappings. @param prefix: A namespace prefix to resolve. @type prefix: basestring @param default: An optional value to be returned when the prefix cannot be resolved. @type default: (I{prefix}, I{URI}) @return: The namespace that is mapped to I{prefix} in this context. @rtype: (I{prefix}, I{URI}) """ n = self while n is not None: if prefix in n.nsprefixes: return prefix, n.nsprefixes[prefix] if prefix in self.specialprefixes: return prefix, self.specialprefixes[prefix] n = n.parent return default def addPrefix(self, p, u): """ Add or update a prefix mapping. @param p: A prefix. @type p: basestring @param u: A namespace URI. @type u: basestring @return: self @rtype: L{Element} """ self.nsprefixes[p] = u return self def updatePrefix(self, p, u): """ Update (redefine) a prefix mapping for the branch. @param p: A prefix. @type p: basestring @param u: A namespace URI. @type u: basestring @return: self @rtype: L{Element} @note: This method traverses down the entire branch! """ if p in self.nsprefixes: self.nsprefixes[p] = u for c in self.children: c.updatePrefix(p, u) return self def clearPrefix(self, prefix): """ Clear the specified prefix from the prefix mappings. @param prefix: A prefix to clear. @type prefix: basestring @return: self @rtype: L{Element} """ if prefix in self.nsprefixes: del self.nsprefixes[prefix] return self def findPrefix(self, uri, default=None): """ Find the first prefix that has been mapped to a namespace URI. The local mapping is searched, then walks up the tree until it reaches the top or finds a match. @param uri: A namespace URI. @type uri: basestring @param default: A default prefix when not found. @type default: basestring @return: A mapped prefix. @rtype: basestring """ for item in list(self.nsprefixes.items()): if item[1] == uri: return item[0] for item in list(self.specialprefixes.items()): if item[1] == uri: return item[0] if self.parent is not None: return self.parent.findPrefix(uri, default) return default def findPrefixes(self, uri, match="eq"): """ Find all prefixes that have been mapped to a namespace URI. The local mapping is searched, then walks up the tree until it reaches the top, collecting all matches. @param uri: A namespace URI. @type uri: basestring @param match: A matching function L{Element.matcher}. @type match: basestring @return: A list of mapped prefixes. @rtype: [basestring,...] """ result = [] for item in list(self.nsprefixes.items()): if self.matcher[match](item[1], uri): prefix = item[0] result.append(prefix) for item in list(self.specialprefixes.items()): if self.matcher[match](item[1], uri): prefix = item[0] result.append(prefix) if self.parent is not None: result += self.parent.findPrefixes(uri, match) return result def promotePrefixes(self): """ Push prefix declarations up the tree as far as possible. Prefix mapping are pushed to its parent unless the parent has the prefix mapped to another URI or the parent has the prefix. This is propagated up the tree until the top is reached. @return: self @rtype: L{Element} """ for c in self.children: c.promotePrefixes() if self.parent is None: return for p, u in list(self.nsprefixes.items()): if p in self.parent.nsprefixes: pu = self.parent.nsprefixes[p] if pu == u: del self.nsprefixes[p] continue if p != self.parent.prefix: self.parent.nsprefixes[p] = u del self.nsprefixes[p] return self def refitPrefixes(self): """ Refit namespace qualification by replacing prefixes with explicit namespaces. Also purges prefix mapping table. @return: self @rtype: L{Element} """ for c in self.children: c.refitPrefixes() if self.prefix is not None: ns = self.resolvePrefix(self.prefix) if ns[1] is not None: self.expns = ns[1] self.prefix = None self.nsprefixes = {} return self def normalizePrefixes(self): """ Normalize the namespace prefixes. This generates unique prefixes for all namespaces. Then retrofits all prefixes and prefix mappings. Further, it will retrofix attribute values that have values containing (:). @return: self @rtype: L{Element} """ PrefixNormalizer.apply(self) return self def isempty(self, content=True): """ Get whether the element has no children. @param content: Test content (children & text) only. @type content: boolean @return: True when element has not children. @rtype: boolean """ nochildren = not self.children notext = self.text is None nocontent = nochildren and notext if content: return nocontent noattrs = not len(self.attributes) return nocontent and noattrs def isnil(self): """ Get whether the element is I{nil} as defined by having an I{xsi:nil="true"} attribute. @return: True if I{nil}, else False @rtype: boolean """ nilattr = self.getAttribute("nil", ns=Namespace.xsins) return nilattr is not None and (nilattr.getValue().lower() == "true") def setnil(self, flag=True): """ Set this node to I{nil} as defined by having an I{xsi:nil}=I{flag} attribute. @param flag: A flag indicating how I{xsi:nil} will be set. @type flag: boolean @return: self @rtype: L{Element} """ p, u = Namespace.xsins name = ":".join((p, "nil")) self.set(name, str(flag).lower()) self.addPrefix(p, u) if flag: self.text = None return self def applyns(self, ns): """ Apply the namespace to this node. If the prefix is I{None} then this element's explicit namespace I{expns} is set to the URI defined by I{ns}. Otherwise, the I{ns} is simply mapped. @param ns: A namespace. @type ns: (I{prefix}, I{URI}) """ if ns is None: return if not isinstance(ns, (list, tuple)): raise Exception("namespace must be a list or a tuple") if ns[0] is None: self.expns = ns[1] else: self.prefix = ns[0] self.nsprefixes[ns[0]] = ns[1] def str(self, indent=0): """ Get a string representation of this XML fragment. @param indent: The indent to be used in formatting the output. @type indent: int @return: A I{pretty} string. @rtype: basestring """ tab = "%*s" % (indent * 3, "") result = [] result.append("%s<%s" % (tab, self.qname())) result.append(self.nsdeclarations()) for a in self.attributes: result.append(" %s" % (str(a),)) if self.isempty(): result.append("/>") return "".join(result) result.append(">") if self.hasText(): result.append(self.text.escape()) for c in self.children: result.append("\n") result.append(c.str(indent + 1)) if len(self.children): result.append("\n%s" % (tab,)) result.append("" % (self.qname(),)) return "".join(result) def plain(self): """ Get a string representation of this XML fragment. @return: A I{plain} string. @rtype: basestring """ result = ["<%s" % (self.qname(),), self.nsdeclarations()] for a in self.attributes: result.append(" %s" % (str(a),)) if self.isempty(): result.append("/>") return "".join(result) result.append(">") if self.hasText(): result.append(self.text.escape()) for c in self.children: result.append(c.plain()) result.append("" % (self.qname(),)) return "".join(result) def nsdeclarations(self): """ Get a string representation for all namespace declarations as xmlns="" and xmlns:p="". @return: A separated list of declarations. @rtype: basestring """ s = [] myns = None, self.expns if self.parent is None: pns = Namespace.default else: pns = None, self.parent.expns if myns[1] != pns[1]: if self.expns is not None: s.append(' xmlns="%s"' % (self.expns,)) for item in list(self.nsprefixes.items()): p, u = item if self.parent is not None: ns = self.parent.resolvePrefix(p) if ns[1] == u: continue s.append(' xmlns:%s="%s"' % (p, u)) return "".join(s) def match(self, name=None, ns=None): """ Match by (optional) name and/or (optional) namespace. @param name: The optional element tag name. @type name: str @param ns: An optional namespace. @type ns: (I{prefix}, I{name}) @return: True if matched. @rtype: boolean """ byname = name is None or (self.name == name) byns = ns is None or (self.namespace()[1] == ns[1]) return byname and byns def branch(self): """ Get a flattened representation of the branch. @return: A flat list of nodes. @rtype: [L{Element},...] """ branch = [self] for c in self.children: branch += c.branch() return branch def ancestors(self): """ Get a list of ancestors. @return: A list of ancestors. @rtype: [L{Element},...] """ ancestors = [] p = self.parent while p is not None: ancestors.append(p) p = p.parent return ancestors def walk(self, visitor): """ Walk the branch and call the visitor function on each node. @param visitor: A function. @type visitor: single argument function @return: self @rtype: L{Element} """ visitor(self) for c in self.children: c.walk(visitor) return self def prune(self): """Prune the branch of empty nodes.""" pruned = [] for c in self.children: c.prune() if c.isempty(False): pruned.append(c) for p in pruned: self.children.remove(p) def __childrenAtPath(self, parts): result = [] node = self ancestors = parts[:-1] leaf = parts[-1] for name in ancestors: ns = None prefix, name = splitPrefix(name) if prefix is not None: ns = node.resolvePrefix(prefix) child = node.getChild(name, ns) if child is None: break node = child if child is not None: ns = None prefix, leaf = splitPrefix(leaf) if prefix is not None: ns = node.resolvePrefix(prefix) result = child.getChildren(leaf) return result def __len__(self): return len(self.children) def __getitem__(self, index): if isinstance(index, str): return self.get(index) if index < len(self.children): return self.children[index] def __setitem__(self, index, value): if isinstance(index, str): self.set(index, value) else: if index < len(self.children) and isinstance(value, Element): self.children.insert(index, value) def __eq__(self, rhs): return (isinstance(rhs, Element) and self.match(rhs.name, rhs.namespace())) def __repr__(self): return "Element (prefix=%s, name=%s)" % (self.prefix, self.name) def __unicode__(self): return self.str() def __iter__(self): return NodeIterator(self) class NodeIterator: """ The L{Element} child node iterator. @ivar pos: The current position @type pos: int @ivar children: A list of a child nodes. @type children: [L{Element},...] """ def __init__(self, parent): """ @param parent: An element to iterate. @type parent: L{Element} """ self.pos = 0 self.children = parent.children def __next__(self): """ Get the next child. @return: The next child. @rtype: L{Element} @raise StopIterator: At the end. """ try: child = self.children[self.pos] self.pos += 1 return child except Exception: raise StopIteration() class PrefixNormalizer: """ The prefix normalizer provides namespace prefix normalization. @ivar node: A node to normalize. @type node: L{Element} @ivar branch: The nodes flattened branch. @type branch: [L{Element},...] @ivar namespaces: A unique list of namespaces (URI). @type namespaces: [str,...] @ivar prefixes: A reverse dict of prefixes. @type prefixes: {u: p} """ @classmethod def apply(cls, node): """ Normalize the specified node. @param node: A node to normalize. @type node: L{Element} @return: The normalized node. @rtype: L{Element} """ return PrefixNormalizer(node).refit() def __init__(self, node): """ @param node: A node to normalize. @type node: L{Element} """ self.node = node self.branch = node.branch() self.namespaces = self.getNamespaces() self.prefixes = self.genPrefixes() def getNamespaces(self): """ Get the I{unique} set of namespaces referenced in the branch. @return: A set of namespaces. @rtype: set """ s = set() for n in self.branch + self.node.ancestors(): if self.permit(n.expns): s.add(n.expns) s = s.union(self.pset(n)) return s def pset(self, n): """ Convert the nodes nsprefixes into a set. @param n: A node. @type n: L{Element} @return: A set of namespaces. @rtype: set """ s = set() for ns in list(n.nsprefixes.items()): if self.permit(ns): s.add(ns[1]) return s def genPrefixes(self): """ Generate a I{reverse} mapping of unique prefixes for all namespaces. @return: A reverse dict of prefixes. @rtype: {u: p} """ prefixes = {} n = 0 for u in self.namespaces: prefixes[u] = "ns%d" % (n,) n += 1 return prefixes def refit(self): """Refit (normalize) the prefixes in the node.""" self.refitNodes() self.refitMappings() def refitNodes(self): """Refit (normalize) all of the nodes in the branch.""" for n in self.branch: if n.prefix is not None: ns = n.namespace() if self.permit(ns): n.prefix = self.prefixes[ns[1]] self.refitAttrs(n) def refitAttrs(self, n): """ Refit (normalize) all of the attributes in the node. @param n: A node. @type n: L{Element} """ for a in n.attributes: self.refitAddr(a) def refitAddr(self, a): """ Refit (normalize) the attribute. @param a: An attribute. @type a: L{Attribute} """ if a.prefix is not None: ns = a.namespace() if self.permit(ns): a.prefix = self.prefixes[ns[1]] self.refitValue(a) def refitValue(self, a): """ Refit (normalize) the attribute's value. @param a: An attribute. @type a: L{Attribute} """ p, name = splitPrefix(a.getValue()) if p is None: return ns = a.resolvePrefix(p) if self.permit(ns): p = self.prefixes[ns[1]] a.setValue(":".join((p, name))) def refitMappings(self): """Refit (normalize) all of the nsprefix mappings.""" for n in self.branch: n.nsprefixes = {} n = self.node for u, p in list(self.prefixes.items()): n.addPrefix(p, u) def permit(self, ns): """ Get whether the I{ns} is to be normalized. @param ns: A namespace. @type ns: (p, u) @return: True if to be included. @rtype: boolean """ return not self.skip(ns) def skip(self, ns): """ Get whether the I{ns} is to B{not} be normalized. @param ns: A namespace. @type ns: (p, u) @return: True if to be skipped. @rtype: boolean """ return ns is None or ns in ( Namespace.default, Namespace.xsdns, Namespace.xsins, Namespace.xmlns) suds-1.1.2/suds/sax/enc.py000066400000000000000000000052521425611400200153600ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Provides XML I{special character} encoder classes. """ import re class Encoder: """ An XML special character encoder/decoder. @cvar encodings: A mapping of special characters encoding. @type encodings: [(str, str),...] @cvar decodings: A mapping of special characters decoding. @type decodings: [(str, str),...] @cvar special: A list of special characters. @type special: [char,...] """ encodings = ( ("&(?!(amp|lt|gt|quot|apos);)", "&"), ("<", "<"), (">", ">"), ('"', """), ("'", "'")) decodings = ( ("<", "<"), (">", ">"), (""", '"'), ("'", "'"), ("&", "&")) special = ("&", "<", ">", '"', "'") def encode(self, s): """ Encode special characters found in string I{s}. @param s: A string to encode. @type s: str @return: The encoded string. @rtype: str """ if isinstance(s, str) and self.__needs_encoding(s): for x in self.encodings: s = re.sub(x[0], x[1], s) return s def decode(self, s): """ Decode special characters encodings found in string I{s}. @param s: A string to decode. @type s: str @return: The decoded string. @rtype: str """ if isinstance(s, str) and "&" in s: for x in self.decodings: s = s.replace(x[0], x[1]) return s def __needs_encoding(self, s): """ Get whether string I{s} contains special characters. @param s: A string to check. @type s: str @return: True if needs encoding. @rtype: boolean """ if isinstance(s, str): for c in self.special: if c in s: return True suds-1.1.2/suds/sax/parser.py000066400000000000000000000101631425611400200161040ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Classes providing a (D)ocument (O)bject (M)odel representation of an XML document. The goal is to provide an easy, intuitive interface for managing XML documents. Although the term DOM is used above, this model is B{far} better. XML namespaces in suds are represented using a (2) element tuple containing the prefix and the URI, e.g. I{('tns', 'http://myns')}. """ import suds from suds import * from suds.sax import * from suds.sax.attribute import Attribute from suds.sax.document import Document from suds.sax.element import Element from suds.sax.text import Text import sys from xml.sax import make_parser, InputSource, ContentHandler from xml.sax.handler import feature_external_ges class Handler(ContentHandler): """SAX handler.""" def __init__(self): self.nodes = [Document()] def startElement(self, name, attrs): top = self.top() node = Element(str(name)) for a in attrs.getNames(): n = str(a) v = str(attrs.getValue(a)) attribute = Attribute(n, v) if self.mapPrefix(node, attribute): continue node.append(attribute) node.charbuffer = [] top.append(node) self.push(node) def mapPrefix(self, node, attribute): if attribute.name == "xmlns": if len(attribute.value): node.expns = str(attribute.value) return True if attribute.prefix == "xmlns": prefix = attribute.name node.nsprefixes[prefix] = str(attribute.value) return True return False def endElement(self, name): name = str(name) current = self.pop() if name != current.qname(): raise Exception("malformed document") if current.charbuffer: current.text = Text("".join(current.charbuffer)) del current.charbuffer if current: current.trim() def characters(self, content): text = str(content) node = self.top() node.charbuffer.append(text) def push(self, node): self.nodes.append(node) return node def pop(self): return self.nodes.pop() def top(self): return self.nodes[-1] class Parser: """SAX parser.""" @classmethod def saxparser(cls): p = make_parser() p.setFeature(feature_external_ges, 0) h = Handler() p.setContentHandler(h) return p, h def parse(self, file=None, string=None): """ SAX parse XML text. @param file: Parse a python I{file-like} object. @type file: I{file-like} object @param string: Parse string XML. @type string: str @return: Parsed XML document. @rtype: L{Document} """ if file is None and string is None: return timer = suds.metrics.Timer() timer.start() source = file if file is None: source = InputSource(None) source.setByteStream(suds.BytesIO(string)) sax, handler = self.saxparser() sax.parse(source) timer.stop() if file is None: suds.metrics.log.debug("%s\nsax duration: %s", string, timer) else: suds.metrics.log.debug("sax (%s) duration: %s", file, timer) return handler.nodes[0] suds-1.1.2/suds/sax/text.py000066400000000000000000000066531425611400200156050ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Contains XML text classes. """ from suds import * from suds.sax import * class Text(str): """ An XML text object used to represent text content. @ivar lang: The (optional) language flag. @type lang: bool @ivar escaped: The (optional) XML special character escaped flag. @type escaped: bool """ __slots__ = ('lang', 'escaped') @classmethod def __valid(cls, *args): return len(args) and args[0] is not None def __new__(cls, *args, **kwargs): if cls.__valid(*args): lang = kwargs.pop('lang', None) escaped = kwargs.pop('escaped', False) result = super(Text, cls).__new__(cls, *args, **kwargs) result.lang = lang result.escaped = escaped else: result = None return result def escape(self): """ Encode (escape) special XML characters. @return: The text with XML special characters escaped. @rtype: L{Text} """ if not self.escaped: post = sax.encoder.encode(self) escaped = ( post != self ) return Text(post, lang=self.lang, escaped=escaped) return self def unescape(self): """ Decode (unescape) special XML characters. @return: The text with escaped XML special characters decoded. @rtype: L{Text} """ if self.escaped: post = sax.encoder.decode(self) return Text(post, lang=self.lang) return self def trim(self): post = self.strip() return Text(post, lang=self.lang, escaped=self.escaped) def __add__(self, other): joined = ''.join((self, other)) result = Text(joined, lang=self.lang, escaped=self.escaped) if isinstance(other, Text): result.escaped = self.escaped or other.escaped return result def __repr__(self): s = [self] if self.lang is not None: s.append(' [%s]' % self.lang) if self.escaped: s.append(' ') return ''.join(s) def __getstate__(self): state = {} for k in self.__slots__: state[k] = getattr(self, k) return state def __setstate__(self, state): for k in self.__slots__: setattr(self, k, state[k]) class Raw(Text): """ Raw text which is not XML escaped. This may include I{string} XML. """ def escape(self): return self def unescape(self): return self def __add__(self, other): joined = ''.join((self, other)) return Raw(joined, lang=self.lang) suds-1.1.2/suds/servicedefinition.py000066400000000000000000000204311425611400200175250ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ The I{service definition} provides a textual representation of a service. """ from suds import * import suds.metrics as metrics from suds.sax import Namespace from logging import getLogger log = getLogger(__name__) class ServiceDefinition(UnicodeMixin): """ A service definition provides an object used to generate a textual description of a service. @ivar wsdl: A wsdl. @type wsdl: L{wsdl.Definitions} @ivar sort_namespaces: Whether to sort namespaces on storing them. @ivar service: The service object. @type service: L{suds.wsdl.Service} @ivar ports: A list of port-tuple: (port, [(method-name, pdef)]) @type ports: [port-tuple,..] @ivar prefixes: A list of remapped prefixes. @type prefixes: [(prefix,uri),..] @ivar types: A list of type definitions @type types: [I{Type},..] """ def __init__(self, wsdl, service): """ @param wsdl: A WSDL object @type wsdl: L{Definitions} @param service: A service B{name}. @type service: str @param service: A service B{name}. @param sort_namespaces: Whether to sort namespaces on storing them. """ self.wsdl = wsdl self.service = service self.ports = [] self.params = [] self.types = [] self.prefixes = [] self.addports() self.paramtypes() self.publictypes() self.getprefixes() self.pushprefixes() def pushprefixes(self): """ Add our prefixes to the WSDL so that when users invoke methods and reference the prefixes, they will resolve properly. """ for ns in self.prefixes: self.wsdl.root.addPrefix(ns[0], ns[1]) def addports(self): """ Look through the list of service ports and construct a list of tuples where each tuple is used to describe a port and its list of methods as: (port, [method]). Each method is a tuple: (name, [pdef,..]) where each pdef is a tuple: (param-name, type). """ timer = metrics.Timer() timer.start() for port in self.service.ports: p = self.findport(port) for op in list(port.binding.operations.values()): m = p[0].method(op.name) binding = m.binding.input method = (m.name, binding.param_defs(m)) p[1].append(method) metrics.log.debug("method '%s' created: %s", m.name, timer) p[1].sort() timer.stop() def findport(self, port): """ Find and return a port tuple for the specified port. Created and added when not found. @param port: A port. @type port: I{service.Port} @return: A port tuple. @rtype: (port, [method]) """ for p in self.ports: if p[0] == p: return p p = (port, []) self.ports.append(p) return p def getprefixes(self): """Add prefixes for each namespace referenced by parameter types.""" namespaces = [] for l in (self.params, self.types): for t,r in l: ns = r.namespace() if ns[1] is None: continue if ns[1] in namespaces: continue if Namespace.xs(ns) or Namespace.xsd(ns): continue namespaces.append(ns[1]) if t == r: continue ns = t.namespace() if ns[1] is None: continue if ns[1] in namespaces: continue namespaces.append(ns[1]) i = 0 if self.wsdl.options.sortNamespaces: namespaces.sort() for u in namespaces: p = self.nextprefix() ns = (p, u) self.prefixes.append(ns) def paramtypes(self): """Get all parameter types.""" for m in [p[1] for p in self.ports]: for p in [p[1] for p in m]: for pd in p: if pd[1] in self.params: continue item = (pd[1], pd[1].resolve()) self.params.append(item) def publictypes(self): """Get all public types.""" for t in list(self.wsdl.schema.types.values()): if t in self.params: continue if t in self.types: continue item = (t, t) self.types.append(item) self.types.sort(key=lambda x: x[0].name) def nextprefix(self): """ Get the next available prefix. This means a prefix starting with 'ns' with a number appended as (ns0, ns1, ..) that is not already defined in the WSDL document. """ used = [ns[0] for ns in self.prefixes] used += [ns[0] for ns in list(self.wsdl.root.nsprefixes.items())] for n in range(0,1024): p = 'ns%d'%n if p not in used: return p raise Exception('prefixes exhausted') def getprefix(self, u): """ Get the prefix for the specified namespace (URI) @param u: A namespace URI. @type u: str @return: The namspace. @rtype: (prefix, uri). """ for ns in Namespace.all: if u == ns[1]: return ns[0] for ns in self.prefixes: if u == ns[1]: return ns[0] raise Exception('ns (%s) not mapped' % u) def xlate(self, type): """ Get a (namespace) translated I{qualified} name for specified type. @param type: A schema type. @type type: I{suds.xsd.sxbasic.SchemaObject} @return: A translated I{qualified} name. @rtype: str """ resolved = type.resolve() name = resolved.name if type.multi_occurrence(): name += '[]' ns = resolved.namespace() if ns[1] == self.wsdl.tns[1]: return name prefix = self.getprefix(ns[1]) return ':'.join((prefix, name)) def description(self): """ Get a textual description of the service for which this object represents. @return: A textual description. @rtype: str """ s = [] indent = (lambda n : '\n%*s'%(n*3,' ')) s.append('Service ( %s ) tns="%s"' % (self.service.name, self.wsdl.tns[1])) s.append(indent(1)) s.append('Prefixes (%d)' % len(self.prefixes)) for p in self.prefixes: s.append(indent(2)) s.append('%s = "%s"' % p) s.append(indent(1)) s.append('Ports (%d):' % len(self.ports)) for p in self.ports: s.append(indent(2)) s.append('(%s)' % p[0].name) s.append(indent(3)) s.append('Methods (%d):' % len(p[1])) for m in p[1]: sig = [] s.append(indent(4)) sig.append(m[0]) sig.append('(') sig.append(', '.join("%s %s" % (self.xlate(p[1]), p[0]) for p in m[1])) sig.append(')') try: s.append(''.join(sig)) except Exception: pass s.append(indent(3)) s.append('Types (%d):' % len(self.types)) for t in self.types: s.append(indent(4)) s.append(self.xlate(t[0])) s.append('\n\n') return ''.join(s) def __unicode__(self): try: return self.description() except Exception as e: log.exception(e) return tostr(e) suds-1.1.2/suds/serviceproxy.py000066400000000000000000000054221425611400200165610ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ The service proxy provides access to web services. Replaced by: L{client.Client} """ from suds import * from suds.client import Client class ServiceProxy(UnicodeMixin): """ A lightweight soap based web service proxy. @ivar __client__: A client. Everything is delegated to the 2nd generation API. @type __client__: L{Client} @note: Deprecated, replaced by L{Client}. """ def __init__(self, url, **kwargs): """ @param url: The URL for the WSDL. @type url: str @param kwargs: keyword arguments. @keyword faults: Raise faults raised by server (default:True), else return tuple from service method invocation as (http code, object). @type faults: boolean @keyword proxy: An http proxy to be specified on requests (default:{}). The proxy is defined as {protocol:proxy,} @type proxy: dict """ client = Client(url, **kwargs) self.__client__ = client def get_instance(self, name): """ Get an instance of a WSDL type by name @param name: The name of a type defined in the WSDL. @type name: str @return: An instance on success, else None @rtype: L{sudsobject.Object} """ return self.__client__.factory.create(name) def get_enum(self, name): """ Get an instance of an enumeration defined in the WSDL by name. @param name: The name of a enumeration defined in the WSDL. @type name: str @return: An instance on success, else None @rtype: L{sudsobject.Object} """ return self.__client__.factory.create(name) def __unicode__(self): return str(self.__client__) def __getattr__(self, name): builtin = name.startswith('__') and name.endswith('__') if builtin: return self.__dict__[name] else: return getattr(self.__client__.service, name) suds-1.1.2/suds/soaparray.py000066400000000000000000000042751425611400200160250ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ The I{soaparray} module provides XSD extensions for handling soap (section 5) encoded arrays. """ from suds import * from logging import getLogger from suds.xsd.sxbasic import Factory as SXFactory from suds.xsd.sxbasic import Attribute as SXAttribute class Attribute(SXAttribute): """ Represents an XSD that handles special attributes that are extensions for WSDLs. @ivar aty: Array type information. @type aty: The value of wsdl:arrayType. """ def __init__(self, schema, root, aty): """ @param aty: Array type information. @type aty: The value of wsdl:arrayType. """ SXAttribute.__init__(self, schema, root) if aty.endswith('[]'): self.aty = aty[:-2] else: self.aty = aty def autoqualified(self): aqs = SXAttribute.autoqualified(self) aqs.append('aty') return aqs def description(self): d = SXAttribute.description(self) d = d+('aty',) return d # # Builder function, only builds Attribute when arrayType # attribute is defined on root. # def __fn(x, y): ns = (None, "http://schemas.xmlsoap.org/wsdl/") aty = y.get('arrayType', ns=ns) if aty is None: return SXAttribute(x, y) return Attribute(x, y, aty) # # Remap tags to __fn() builder. # SXFactory.maptag('attribute', __fn) suds-1.1.2/suds/store.py000066400000000000000000000446501425611400200151610ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Support for holding XML document content that may then be accessed internally by suds without having to download them from an external source. Also contains XML document content to be distributed alongside the suds library. """ import suds soap5_encoding_schema = suds.byte_str("""\ 'root' can be used to distinguish serialization roots from other elements that are present in a serialization but are not roots of a serialized value graph Attributes common to all elements that function as accessors or represent independent (multi-ref) values. The href attribute is intended to be used in a manner like CONREF. That is, the element content should be empty iff the href attribute appears 'Array' is a complex type for accessors identified by position """) class DocumentStore(object): """ Local XML document content repository. Each XML document is identified by its location, i.e. URL without any protocol identifier. Contained XML documents can be looked up using any URL referencing that same location. """ def __init__(self, *args, **kwargs): self.__store = { 'schemas.xmlsoap.org/soap/encoding/': soap5_encoding_schema} self.update = self.__store.update self.update(*args, **kwargs) def __len__(self): return len(self.__store) def open(self, url): """ Open a document at the specified URL. The document URL's needs not contain a protocol identifier, and if it does, that protocol identifier is ignored when looking up the store content. Missing documents referenced using the internal 'suds' protocol are reported by raising an exception. For other protocols, None is returned instead. @param url: A document URL. @type url: str @return: Document content or None if not found. @rtype: bytes """ protocol, location = self.__split(url) content = self.__find(location) if protocol == 'suds' and content is None: raise Exception('location "%s" not in document store' % location) return content def __find(self, location): """ Find the specified location in the store. @param location: The I{location} part of a URL. @type location: str @return: Document content or None if not found. @rtype: bytes """ return self.__store.get(location) def __split(self, url): """ Split the given URL into its I{protocol} & I{location} components. @param url: A URL. @param url: str @return: (I{protocol}, I{location}) @rtype: (str, str) """ parts = url.split('://', 1) if len(parts) == 2: return parts return None, url defaultDocumentStore = DocumentStore() suds-1.1.2/suds/sudsobject.py000066400000000000000000000252261425611400200161700ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Provides a collection of suds objects primarily used for highly dynamic interactions with WSDL/XSD defined types. """ from suds import * from logging import getLogger log = getLogger(__name__) def items(sobject): """ Extract the I{items} from a suds object. Much like the items() method works on I{dict}. @param sobject: A suds object @type sobject: L{Object} @return: A list of items contained in I{sobject}. @rtype: [(key, value),...] """ for item in sobject: yield item def asdict(sobject): """ Convert a sudsobject into a dictionary. @param sobject: A suds object @type sobject: L{Object} @return: A python dictionary containing the items contained in I{sobject}. @rtype: dict """ return dict(items(sobject)) def merge(a, b): """ Merge all attributes and metadata from I{a} to I{b}. @param a: A I{source} object @type a: L{Object} @param b: A I{destination} object @type b: L{Object} """ for item in a: setattr(b, item[0], item[1]) b.__metadata__ = b.__metadata__ return b def footprint(sobject): """ Get the I{virtual footprint} of the object. This is really a count of all the significant value attributes in the branch. @param sobject: A suds object. @type sobject: L{Object} @return: The branch footprint. @rtype: int """ n = 0 for a in sobject.__keylist__: v = getattr(sobject, a) if v is None: continue if isinstance(v, Object): n += footprint(v) continue if hasattr(v, "__len__"): if len(v): n += 1 continue n += 1 return n class Factory: cache = {} @classmethod def subclass(cls, name, bases, dict={}): if not isinstance(bases, tuple): bases = (bases,) # name is of type unicode in python 2 -> not accepted by type() name = str(name) key = ".".join((name, str(bases))) subclass = cls.cache.get(key) if subclass is None: subclass = type(name, bases, dict) cls.cache[key] = subclass return subclass @classmethod def object(cls, classname=None, dict={}): if classname is not None: subclass = cls.subclass(classname, Object) inst = subclass() else: inst = Object() for a in list(dict.items()): setattr(inst, a[0], a[1]) return inst @classmethod def metadata(cls): return Metadata() @classmethod def property(cls, name, value=None): subclass = cls.subclass(name, Property) return subclass(value) class Object(UnicodeMixin): def __init__(self): self.__keylist__ = [] self.__printer__ = Printer() self.__metadata__ = Metadata() def __setattr__(self, name, value): builtin = name.startswith("__") and name.endswith("__") if not builtin and name not in self.__keylist__: self.__keylist__.append(name) self.__dict__[name] = value def __delattr__(self, name): try: del self.__dict__[name] builtin = name.startswith("__") and name.endswith("__") if not builtin: self.__keylist__.remove(name) except Exception: cls = self.__class__.__name__ raise AttributeError("%s has no attribute '%s'" % (cls, name)) def __getitem__(self, name): if isinstance(name, int): name = self.__keylist__[int(name)] return getattr(self, name) def __setitem__(self, name, value): setattr(self, name, value) def __iter__(self): return Iter(self) def __len__(self): return len(self.__keylist__) def __contains__(self, name): return name in self.__keylist__ def __repr__(self): return str(self) def __unicode__(self): return self.__printer__.tostr(self) class Iter: def __init__(self, sobject): self.sobject = sobject self.keylist = self.__keylist(sobject) self.index = 0 def __next__(self): keylist = self.keylist nkeys = len(self.keylist) while self.index < nkeys: k = keylist[self.index] self.index += 1 if hasattr(self.sobject, k): v = getattr(self.sobject, k) return (k, v) raise StopIteration() def __keylist(self, sobject): keylist = sobject.__keylist__ try: keyset = set(keylist) ordering = sobject.__metadata__.ordering ordered = set(ordering) if not ordered.issuperset(keyset): log.debug("%s must be superset of %s, ordering ignored", keylist, ordering) raise KeyError() return ordering except Exception: return keylist def __iter__(self): return self class Metadata(Object): def __init__(self): self.__keylist__ = [] self.__printer__ = Printer() class Facade(Object): def __init__(self, name): Object.__init__(self) md = self.__metadata__ md.facade = name class Property(Object): def __init__(self, value): Object.__init__(self) self.value = value def items(self): for item in self: if item[0] != "value": yield item def get(self): return self.value def set(self, value): self.value = value return self class Printer: """Pretty printing of a Object object.""" @classmethod def indent(cls, n): return "%*s" % (n * 3, " ") def tostr(self, object, indent=-2): """Get s string representation of object.""" history = [] return self.process(object, history, indent) def process(self, object, h, n=0, nl=False): """Print object using the specified indent (n) and newline (nl).""" if object is None: return "None" if isinstance(object, Object): if len(object) == 0: return "" return self.print_object(object, h, n + 2, nl) if isinstance(object, dict): if len(object) == 0: return "" return self.print_dictionary(object, h, n + 2, nl) if isinstance(object, (list, tuple)): if len(object) == 0: return "" return self.print_collection(object, h, n + 2) if isinstance(object, str): return '"%s"' % (tostr(object),) return "%s" % (tostr(object),) def print_object(self, d, h, n, nl=False): """Print complex using the specified indent (n) and newline (nl).""" s = [] cls = d.__class__ if d in h: s.append("(") s.append(cls.__name__) s.append(")") s.append("...") return "".join(s) h.append(d) if nl: s.append("\n") s.append(self.indent(n)) if cls != Object: s.append("(") if isinstance(d, Facade): s.append(d.__metadata__.facade) else: s.append(cls.__name__) s.append(")") s.append("{") for item in d: if self.exclude(d, item): continue item = self.unwrap(d, item) s.append("\n") s.append(self.indent(n+1)) if isinstance(item[1], (list,tuple)): s.append(item[0]) s.append("[]") else: s.append(item[0]) s.append(" = ") s.append(self.process(item[1], h, n, True)) s.append("\n") s.append(self.indent(n)) s.append("}") h.pop() return "".join(s) def print_dictionary(self, d, h, n, nl=False): """Print complex using the specified indent (n) and newline (nl).""" if d in h: return "{}..." h.append(d) s = [] if nl: s.append("\n") s.append(self.indent(n)) s.append("{") for item in list(d.items()): s.append("\n") s.append(self.indent(n+1)) if isinstance(item[1], (list,tuple)): s.append(tostr(item[0])) s.append("[]") else: s.append(tostr(item[0])) s.append(" = ") s.append(self.process(item[1], h, n, True)) s.append("\n") s.append(self.indent(n)) s.append("}") h.pop() return "".join(s) def print_collection(self, c, h, n): """Print collection using the specified indent (n) and newline (nl).""" if c in h: return "[]..." h.append(c) s = [] for item in c: s.append("\n") s.append(self.indent(n)) s.append(self.process(item, h, n - 2)) s.append(",") h.pop() return "".join(s) def unwrap(self, d, item): """Translate (unwrap) using an optional wrapper function.""" try: md = d.__metadata__ pmd = getattr(md, "__print__", None) if pmd is None: return item wrappers = getattr(pmd, "wrappers", {}) fn = wrappers.get(item[0], lambda x: x) return (item[0], fn(item[1])) except Exception: pass return item def exclude(self, d, item): """Check metadata for excluded items.""" try: md = d.__metadata__ pmd = getattr(md, "__print__", None) if pmd is None: return False excludes = getattr(pmd, "excludes", []) return item[0] in excludes except Exception: pass return False suds-1.1.2/suds/transport/000077500000000000000000000000001425611400200154765ustar00rootroot00000000000000suds-1.1.2/suds/transport/__init__.py000066400000000000000000000121741425611400200176140ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Contains transport interface (classes). """ from suds import UnicodeMixin import sys class TransportError(Exception): def __init__(self, reason, httpcode, fp=None): Exception.__init__(self, reason) self.httpcode = httpcode self.fp = fp class Request(UnicodeMixin): """ A transport request. Request URL input data may be given as either a byte or a unicode string, but it may not under any circumstances contain non-ASCII characters. The URL value is stored as a str value internally. With Python versions prior to 3.0, str is the byte string type, while with later Python versions it is the unicode string type. @ivar url: The URL for the request. @type url: str @ivar message: The optional message to be sent in the request body. @type message: bytes|None @type timeout: int|None @ivar headers: The HTTP headers to be used for the request. @type headers: dict """ def __init__(self, url, message=None, timeout=None): """ Raised exception in case of detected non-ASCII URL characters may be either UnicodeEncodeError or UnicodeDecodeError, depending on the used Python version's str type and the exact value passed as URL input data. @param url: The URL for the request. @type url: bytes|str|unicode @param message: The optional message to be sent in the request body. @type message: bytes|None """ self.__set_URL(url) self.headers = {} self.message = message self.timeout = timeout def __unicode__(self): result = ["URL: %s\nHEADERS: %s" % (self.url, self.headers)] if self.message is not None: result.append("MESSAGE:") result.append(self.message.decode("raw_unicode_escape")) return "\n".join(result) def __set_URL(self, url): """ URL is stored as a str internally and must not contain ASCII chars. Raised exception in case of detected non-ASCII URL characters may be either UnicodeEncodeError or UnicodeDecodeError, depending on the used Python version's str type and the exact value passed as URL input data. """ if isinstance(url, str): url.encode("ascii") # Check for non-ASCII characters. self.url = url elif sys.version_info < (3, 0): self.url = url.encode("ascii") else: self.url = url.decode("ascii") class Reply(UnicodeMixin): """ A transport reply. @ivar code: The HTTP code returned. @type code: int @ivar headers: The HTTP headers included in the received reply. @type headers: dict @ivar message: The message received as a reply. @type message: bytes """ def __init__(self, code, headers, message): """ @param code: The HTTP code returned. @type code: int @param headers: The HTTP headers included in the received reply. @type headers: dict @param message: The (optional) message received as a reply. @type message: bytes """ self.code = code self.headers = headers self.message = message def __unicode__(self): return """\ CODE: %s HEADERS: %s MESSAGE: %s""" % (self.code, self.headers, self.message.decode("raw_unicode_escape")) class Transport(object): """The transport I{interface}.""" def __init__(self): from suds.transport.options import Options self.options = Options() def open(self, request): """ Open the URL in the specified request. @param request: A transport request. @type request: L{Request} @return: An input stream. @rtype: stream @raise TransportError: On all transport errors. """ raise Exception('not-implemented') def send(self, request): """ Send SOAP message. Implementations are expected to handle: - proxies - I{HTTP} headers - cookies - sending message - brokering exceptions into L{TransportError} @param request: A transport request. @type request: L{Request} @return: The reply @rtype: L{Reply} @raise TransportError: On all transport errors. """ raise Exception('not-implemented') suds-1.1.2/suds/transport/http.py000066400000000000000000000202231425611400200170260ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Basic HTTP transport implementation classes. """ from suds.properties import Unskin from suds.transport import * import base64 from http.cookiejar import CookieJar import http.client import socket import sys import urllib.request, urllib.error, urllib.parse import gzip import zlib from logging import getLogger log = getLogger(__name__) class HttpTransport(Transport): """ Basic HTTP transport implemented using using urllib2, that provides for cookies & proxies but no authentication. """ def __init__(self, **kwargs): """ @param kwargs: Keyword arguments. - B{proxy} - An HTTP proxy to be specified on requests. The proxy is defined as {protocol:proxy,} - type: I{dict} - default: {} - B{timeout} - Set the URL open timeout (seconds). - type: I{float} - default: 90 """ Transport.__init__(self) Unskin(self.options).update(kwargs) self.cookiejar = CookieJar() self.proxy = {} self.urlopener = None def open(self, request): try: url = self.__get_request_url_for_urllib(request) headers = request.headers log.debug('opening (%s)', url) u2request = urllib.request.Request(url, headers=headers) self.proxy = self.options.proxy return self.u2open(u2request) except urllib.error.HTTPError as e: raise TransportError(str(e), e.code, e.fp) def send(self, request): url = self.__get_request_url_for_urllib(request) msg = request.message headers = request.headers if 'Content-Encoding' in headers: encoding = headers['Content-Encoding'] if encoding == 'gzip': msg = gzip.compress(msg) elif encoding == 'deflate': msg = zlib.compress(msg) try: u2request = urllib.request.Request(url, msg, headers) self.addcookies(u2request) self.proxy = self.options.proxy request.headers.update(u2request.headers) log.debug('sending:\n%s', request) fp = self.u2open(u2request, timeout=request.timeout) self.getcookies(fp, u2request) headers = fp.headers if sys.version_info < (3, 0): headers = headers.dict message = fp.read() if 'Content-Encoding' in headers: encoding = headers['Content-Encoding'] if encoding == 'gzip': message = gzip.decompress(message) elif encoding == 'deflate': message = zlib.decompress(message) reply = Reply(http.client.OK, headers, message) log.debug('received:\n%s', reply) return reply except urllib.error.HTTPError as e: if e.code not in (http.client.ACCEPTED, http.client.NO_CONTENT): raise TransportError(e.msg, e.code, e.fp) def addcookies(self, u2request): """ Add cookies in the cookiejar to the request. @param u2request: A urllib2 request. @rtype: u2request: urllib2.Request. """ self.cookiejar.add_cookie_header(u2request) def getcookies(self, fp, u2request): """ Add cookies in the request to the cookiejar. @param u2request: A urllib2 request. @rtype: u2request: urllib2.Request. """ self.cookiejar.extract_cookies(fp, u2request) def u2open(self, u2request, timeout=None): """ Open a connection. @param u2request: A urllib2 request. @type u2request: urllib2.Request. @return: The opened file-like urllib2 object. @rtype: fp """ tm = timeout or self.options.timeout url = self.u2opener() return url.open(u2request, timeout=tm) def u2opener(self): """ Create a urllib opener. @return: An opener. @rtype: I{OpenerDirector} """ if self.urlopener is None: return urllib.request.build_opener(*self.u2handlers()) return self.urlopener def u2handlers(self): """ Get a collection of urllib handlers. @return: A list of handlers to be installed in the opener. @rtype: [Handler,...] """ return [urllib.request.ProxyHandler(self.proxy)] def u2ver(self): """ Get the major/minor version of the urllib2 lib. @return: The urllib2 version. @rtype: float """ try: return float('%d.%d' % sys.version_info[:2]) except Exception as e: return 0 def __deepcopy__(self, memo={}): clone = self.__class__() p = Unskin(self.options) cp = Unskin(clone.options) cp.update(p) return clone @staticmethod def __get_request_url_for_urllib(request): """ Returns the given request's URL, properly encoded for use with urllib. We expect that the given request object already verified that the URL contains ASCII characters only and stored it as a native str value. urllib accepts URL information as a native str value and may break unexpectedly if given URL information in another format. Python 3.x httplib.client implementation must be given a unicode string and not a bytes object and the given string is internally converted to a bytes object using an explicitly specified ASCII encoding. Python 2.7 httplib implementation expects the URL passed to it to not be a unicode string. If it is, then passing it to the underlying httplib Request object will cause that object to forcefully convert all of its data to unicode, assuming that data contains ASCII data only and raising a UnicodeDecodeError exception if it does not (caused by simple unicode + string concatenation). Python 2.4 httplib implementation does not really care about this as it does not use the internal optimization present in the Python 2.7 implementation causing all the requested data to be converted to unicode. """ assert isinstance(request.url, str) return request.url class HttpAuthenticated(HttpTransport): """ Provides basic HTTP authentication for servers that do not follow the specified challenge/response model. Appends the I{Authorization} HTTP header with base64 encoded credentials on every HTTP request. """ def open(self, request): self.addcredentials(request) return HttpTransport.open(self, request) def send(self, request): self.addcredentials(request) return HttpTransport.send(self, request) def addcredentials(self, request): credentials = self.credentials() if None not in credentials: credentials = ':'.join(credentials) if sys.version_info < (3, 0): encodedString = base64.b64encode(credentials) else: encodedBytes = base64.urlsafe_b64encode(credentials.encode()) encodedString = encodedBytes.decode() request.headers['Authorization'] = 'Basic %s' % encodedString def credentials(self): return self.options.username, self.options.password suds-1.1.2/suds/transport/https.py000066400000000000000000000065701425611400200172220ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Contains classes for authenticated HTTP transport implementations. """ from suds.transport import * from suds.transport.http import HttpTransport import urllib.request, urllib.error, urllib.parse class HttpAuthenticated(HttpTransport): """ Provides basic HTTP authentication that follows the RFC-2617 specification. As defined by specifications, credentials are provided to the server upon request (HTTP/1.0 401 Authorization Required) by the server only. @ivar pm: The password manager. @ivar handler: The authentication handler. """ def __init__(self, **kwargs): """ @param kwargs: Keyword arguments. - B{proxy} - An HTTP proxy to be specified on requests. The proxy is defined as {protocol:proxy,} - type: I{dict} - default: {} - B{timeout} - Set the URL open timeout (seconds). - type: I{float} - default: 90 - B{username} - The username used for HTTP authentication. - type: I{str} - default: None - B{password} - The password used for HTTP authentication. - type: I{str} - default: None """ HttpTransport.__init__(self, **kwargs) self.pm = urllib.request.HTTPPasswordMgrWithDefaultRealm() def open(self, request): self.addcredentials(request) return HttpTransport.open(self, request) def send(self, request): self.addcredentials(request) return HttpTransport.send(self, request) def addcredentials(self, request): credentials = self.credentials() if None not in credentials: u = credentials[0] p = credentials[1] self.pm.add_password(None, request.url, u, p) def credentials(self): return self.options.username, self.options.password def u2handlers(self): handlers = HttpTransport.u2handlers(self) handlers.append(urllib.request.HTTPBasicAuthHandler(self.pm)) return handlers class WindowsHttpAuthenticated(HttpAuthenticated): """ Provides Windows (NTLM) based HTTP authentication. @author: Christopher Bess """ def u2handlers(self): try: from ntlm import HTTPNtlmAuthHandler except ImportError: raise Exception("Cannot import python-ntlm module") handlers = HttpTransport.u2handlers(self) handlers.append(HTTPNtlmAuthHandler.HTTPNtlmAuthHandler(self.pm)) return handlers suds-1.1.2/suds/transport/options.py000066400000000000000000000041671425611400200175530ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Classes modeling transport options. """ from suds.transport import * from suds.properties import * class Options(Skin): """ Options: - B{proxy} - An HTTP proxy to be specified on requests, defined as {protocol:proxy, ...}. - type: I{dict} - default: {} - B{timeout} - Set the URL open timeout (seconds). - type: I{float} - default: 90 - B{headers} - Extra HTTP headers. - type: I{dict} - I{str} B{http} - The I{HTTP} protocol proxy URL. - I{str} B{https} - The I{HTTPS} protocol proxy URL. - default: {} - B{username} - The username used for HTTP authentication. - type: I{str} - default: None - B{password} - The password used for HTTP authentication. - type: I{str} - default: None """ def __init__(self, **kwargs): domain = __name__ definitions = [ Definition('proxy', dict, {}), Definition('timeout', (int,float), 90), Definition('headers', dict, {}), Definition('username', str, None), Definition('password', str, None)] Skin.__init__(self, domain, definitions, kwargs) suds-1.1.2/suds/umx/000077500000000000000000000000001425611400200142535ustar00rootroot00000000000000suds-1.1.2/suds/umx/__init__.py000066400000000000000000000033771425611400200163760ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Provides modules containing classes to support unmarshalling (XML). """ from suds.sudsobject import Object class Content(Object): """ @ivar node: The content source node. @type node: L{sax.element.Element} @ivar data: The (optional) content data. @type data: L{Object} @ivar text: The (optional) content (xml) text. @type text: basestring """ extensions = [] def __init__(self, node, **kwargs): Object.__init__(self) self.node = node self.data = None self.text = None for k,v in list(kwargs.items()): setattr(self, k, v) def __getattr__(self, name): if name not in self.__dict__: if name in self.extensions: v = None setattr(self, name, v) else: raise AttributeError('Content has no attribute %s' % name) else: v = self.__dict__[name] return v suds-1.1.2/suds/umx/attrlist.py000066400000000000000000000053311425611400200164750ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Provides filtered attribute list classes. """ from suds import * from suds.umx import * from suds.sax import Namespace class AttrList: """ A filtered attribute list. Items are included during iteration if they are in either the (xs) or (xml) namespaces. @ivar raw: The I{raw} attribute list. @type raw: list """ def __init__(self, attributes): """ @param attributes: A list of attributes @type attributes: list """ self.raw = attributes def real(self): """ Get list of I{real} attributes which exclude xs and xml attributes. @return: A list of I{real} attributes. @rtype: I{generator} """ for a in self.raw: if self.skip(a): continue yield a def rlen(self): """ Get the number of I{real} attributes which exclude xs and xml attributes. @return: A count of I{real} attributes. @rtype: L{int} """ n = 0 for a in self.real(): n += 1 return n def lang(self): """ Get list of I{filtered} attributes which exclude xs. @return: A list of I{filtered} attributes. @rtype: I{generator} """ for a in self.raw: if a.qname() == 'xml:lang': return a.value return None def skip(self, attr): """ Get whether to skip (filter-out) the specified attribute. @param attr: An attribute. @type attr: I{Attribute} @return: True if should be skipped. @rtype: bool """ ns = attr.namespace() skip = ( Namespace.xmlns[1], 'http://schemas.xmlsoap.org/soap/encoding/', 'http://schemas.xmlsoap.org/soap/envelope/', 'http://www.w3.org/2003/05/soap-envelope', ) return ( Namespace.xs(ns) or ns[1] in skip ) suds-1.1.2/suds/umx/basic.py000066400000000000000000000025521425611400200157120ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Provides basic unmarshaller classes. """ from logging import getLogger from suds import * from suds.umx import * from suds.umx.core import Core class Basic(Core): """ A object builder (unmarshaller). """ def process(self, node): """ Process an object graph representation of the xml I{node}. @param node: An XML tree. @type node: L{sax.element.Element} @return: A suds object. @rtype: L{Object} """ content = Content(node) return Core.process(self, content) suds-1.1.2/suds/umx/core.py000066400000000000000000000166021425611400200155620ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Provides base classes for XML->object I{unmarshalling}. """ from suds import * from suds.umx import * from suds.umx.attrlist import AttrList from suds.sax.text import Text from suds.sudsobject import Factory, merge reserved = {'class':'cls', 'def':'dfn'} class Core: """ The abstract XML I{node} unmarshaller. This class provides the I{core} unmarshalling functionality. """ def process(self, content): """ Process an object graph representation of the xml I{node}. @param content: The current content being unmarshalled. @type content: L{Content} @return: A suds object. @rtype: L{Object} """ self.reset() return self.append(content) def append(self, content): """ Process the specified node and convert the XML document into a I{suds} L{object}. @param content: The current content being unmarshalled. @type content: L{Content} @return: A I{append-result} tuple as: (L{Object}, I{value}) @rtype: I{append-result} @note: This is not the proper entry point. @see: L{process()} """ self.start(content) self.append_attributes(content) self.append_children(content) self.append_text(content) self.end(content) return self.postprocess(content) def postprocess(self, content): """ Perform final processing of the resulting data structure as follows: - Mixed values (children and text) will have a result of the I{content.node}. - Simi-simple values (attributes, no-children and text) will have a result of a property object. - Simple values (no-attributes, no-children with text nodes) will have a string result equal to the value of the content.node.getText(). @param content: The current content being unmarshalled. @type content: L{Content} @return: The post-processed result. @rtype: I{any} """ node = content.node if len(node.children) and node.hasText(): return node attributes = AttrList(node.attributes) if attributes.rlen() and \ not len(node.children) and \ node.hasText(): p = Factory.property(node.name, node.getText()) return merge(content.data, p) if len(content.data): return content.data lang = attributes.lang() if content.node.isnil(): return None if not len(node.children) and content.text is None: if self.nillable(content): return None else: return Text('', lang=lang) if isinstance(content.text, str): return Text(content.text, lang=lang) else: return content.text def append_attributes(self, content): """ Append attribute nodes into L{Content.data}. Attributes in the I{schema} or I{xml} namespaces are skipped. @param content: The current content being unmarshalled. @type content: L{Content} """ attributes = AttrList(content.node.attributes) for attr in attributes.real(): name = attr.name value = attr.value self.append_attribute(name, value, content) def append_attribute(self, name, value, content): """ Append an attribute name/value into L{Content.data}. @param name: The attribute name @type name: basestring @param value: The attribute's value @type value: basestring @param content: The current content being unmarshalled. @type content: L{Content} """ key = name key = '_%s' % reserved.get(key, key) setattr(content.data, key, value) def append_children(self, content): """ Append child nodes into L{Content.data} @param content: The current content being unmarshalled. @type content: L{Content} """ for child in content.node: cont = Content(child) cval = self.append(cont) key = reserved.get(child.name, child.name) if key in content.data: v = getattr(content.data, key) if isinstance(v, list): v.append(cval) else: setattr(content.data, key, [v, cval]) continue if self.multi_occurrence(cont): if cval is None: setattr(content.data, key, []) else: setattr(content.data, key, [cval,]) else: setattr(content.data, key, cval) def append_text(self, content): """ Append text nodes into L{Content.data} @param content: The current content being unmarshalled. @type content: L{Content} """ if content.node.hasText(): content.text = content.node.getText() def reset(self): pass def start(self, content): """ Processing on I{node} has started. Build and return the proper object. @param content: The current content being unmarshalled. @type content: L{Content} @return: A subclass of Object. @rtype: L{Object} """ content.data = Factory.object(content.node.name) def end(self, content): """ Processing on I{node} has ended. @param content: The current content being unmarshalled. @type content: L{Content} """ pass def single_occurrence(self, content): """ Get whether the content has at most a single occurrence (not a list). @param content: The current content being unmarshalled. @type content: L{Content} @return: True if content has at most a single occurrence, else False. @rtype: boolean '""" return not self.multi_occurrence(content) def multi_occurrence(self, content): """ Get whether the content has more than one occurrence (a list). @param content: The current content being unmarshalled. @type content: L{Content} @return: True if content has more than one occurrence, else False. @rtype: boolean '""" return False def nillable(self, content): """ Get whether the object is nillable. @param content: The current content being unmarshalled. @type content: L{Content} @return: True if nillable, else False @rtype: boolean '""" return False suds-1.1.2/suds/umx/encoded.py000066400000000000000000000100161425611400200162240ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Provides soap encoded unmarshaller classes. """ from suds import * from suds.umx import * from suds.umx.typed import Typed from suds.sax import Namespace # # Add encoded extensions # aty = The soap (section 5) encoded array type. # Content.extensions.append('aty') class Encoded(Typed): """ A SOAP section (5) encoding unmarshaller. This marshaller supports rpc/encoded soap styles. """ def start(self, content): # # Grab the array type and continue # self.setaty(content) Typed.start(self, content) def end(self, content): # # Squash soap encoded arrays into python lists. This is # also where we insure that empty arrays are represented # as empty python lists. # aty = content.aty if aty is not None: self.promote(content) return Typed.end(self, content) def postprocess(self, content): # # Ensure proper rendering of empty arrays. # if content.aty is None: return Typed.postprocess(self, content) else: return content.data def setaty(self, content): """ Grab the (aty) soap-enc:arrayType and attach it to the content for proper array processing later in end(). @param content: The current content being unmarshalled. @type content: L{Content} @return: self @rtype: L{Encoded} """ name = 'arrayType' ns = (None, 'http://schemas.xmlsoap.org/soap/encoding/') aty = content.node.get(name, ns) if aty is not None: content.aty = aty parts = aty.split('[') ref = parts[0] if len(parts) == 2: self.applyaty(content, ref) else: pass # (2) dimensional array return self def applyaty(self, content, xty): """ Apply the type referenced in the I{arrayType} to the content (child nodes) of the array. Each element (node) in the array that does not have an explicit xsi:type attribute is given one based on the I{arrayType}. @param content: An array content. @type content: L{Content} @param xty: The XSI type reference. @type xty: str @return: self @rtype: L{Encoded} """ name = 'type' ns = Namespace.xsins parent = content.node for child in parent.getChildren(): ref = child.get(name, ns) if ref is None: parent.addPrefix(ns[0], ns[1]) attr = ':'.join((ns[0], name)) child.set(attr, xty) return self def promote(self, content): """ Promote (replace) the content.data with the first attribute of the current content.data that is a I{list}. Note: the content.data may be empty or contain only _x attributes. In either case, the content.data is assigned an empty list. @param content: An array content. @type content: L{Content} """ for n,v in content.data: if isinstance(v, list): content.data = v return content.data = [] suds-1.1.2/suds/umx/typed.py000066400000000000000000000107551425611400200157620ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Provides typed unmarshaller classes. """ from suds import * from suds.umx import * from suds.umx.core import Core from suds.resolver import NodeResolver, Frame from suds.sudsobject import Factory from logging import getLogger log = getLogger(__name__) # # Add typed extensions # type = The expected xsd type # real = The 'true' XSD type # Content.extensions.append('type') Content.extensions.append('real') class Typed(Core): """ A I{typed} XML unmarshaller @ivar resolver: A schema type resolver. @type resolver: L{NodeResolver} """ def __init__(self, schema): """ @param schema: A schema object. @type schema: L{xsd.schema.Schema} """ self.resolver = NodeResolver(schema) def process(self, node, type): """ Process an object graph representation of the xml L{node}. @param node: An XML tree. @type node: L{sax.element.Element} @param type: The I{optional} schema type. @type type: L{xsd.sxbase.SchemaObject} @return: A suds object. @rtype: L{Object} """ content = Content(node) content.type = type return Core.process(self, content) def reset(self): log.debug('reset') self.resolver.reset() def start(self, content): # # Resolve to the schema type; build an object and setup metadata. # if content.type is None: found = self.resolver.find(content.node) if found is None: log.error(self.resolver.schema) raise TypeNotFound(content.node.qname()) content.type = found else: known = self.resolver.known(content.node) frame = Frame(content.type, resolved=known) self.resolver.push(frame) real = self.resolver.top().resolved content.real = real cls_name = real.name if cls_name is None: cls_name = content.node.name content.data = Factory.object(cls_name) md = content.data.__metadata__ md.sxtype = real def end(self, content): self.resolver.pop() def multi_occurrence(self, content): return content.type.multi_occurrence() def nillable(self, content): resolved = content.type.resolve() return ( content.type.nillable or \ (resolved.builtin() and resolved.nillable ) ) def append_attribute(self, name, value, content): """ Append an attribute name/value into L{Content.data}. @param name: The attribute name @type name: basestring @param value: The attribute's value @type value: basestring @param content: The current content being unmarshalled. @type content: L{Content} """ type = self.resolver.findattr(name) if type is None: log.warning('attribute (%s) type, not-found', name) else: value = self.translated(value, type) Core.append_attribute(self, name, value, content) def append_text(self, content): """ Append text nodes into L{Content.data} Here is where the I{true} type is used to translate the value into the proper python type. @param content: The current content being unmarshalled. @type content: L{Content} """ Core.append_text(self, content) known = self.resolver.top().resolved content.text = self.translated(content.text, known) def translated(self, value, type): """ translate using the schema type """ if value is not None: resolved = type.resolve() return resolved.translate(value) return value suds-1.1.2/suds/version.py000066400000000000000000000021471425611400200155050ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """ Module containing the library's version information. This version information has been extracted into a separate file so it can be read from the setup.py script without having to import the suds package itself. See the setup.py script for more detailed information. """ __version__ = "1.1.2" __build__ = "" suds-1.1.2/suds/wsdl.py000066400000000000000000000777371425611400200150120ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ The I{wsdl} module provides an objectification of the WSDL. The primary class is I{Definitions}, representing the root element found in a WSDL schema document. """ from suds import * from suds.bindings.document import Document from suds.bindings.rpc import RPC, Encoded from suds.reader import DocumentReader from suds.sax.element import Element from suds.sudsobject import Object, Facade, Metadata from suds.xsd import qualify, Namespace from suds.xsd.query import ElementQuery from suds.xsd.schema import Schema, SchemaCollection import re from . import soaparray from urllib.parse import urljoin from logging import getLogger log = getLogger(__name__) wsdlns = (None, "http://schemas.xmlsoap.org/wsdl/") soapns = (None, "http://schemas.xmlsoap.org/wsdl/soap/") soap12ns = (None, "http://schemas.xmlsoap.org/wsdl/soap12/") class WObject(Object): """ Base object for WSDL types. @ivar root: The XML I{root} element. @type root: L{Element} """ def __init__(self, root): """ @param root: An XML root element. @type root: L{Element} """ Object.__init__(self) self.root = root pmd = Metadata() pmd.excludes = ["root"] pmd.wrappers = dict(qname=repr) self.__metadata__.__print__ = pmd self.__resolved = False def resolve(self, definitions): """ Resolve named references to other WSDL objects. Can be safely called multiple times. @param definitions: A definitions object. @type definitions: L{Definitions} """ if not self.__resolved: self.do_resolve(definitions) self.__resolved = True def do_resolve(self, definitions): """ Internal worker resolving named references to other WSDL objects. May only be called once per instance. @param definitions: A definitions object. @type definitions: L{Definitions} """ pass class NamedObject(WObject): """ A B{named} WSDL object. @ivar name: The name of the object. @type name: str @ivar qname: The I{qualified} name of the object. @type qname: (name, I{namespace-uri}). """ def __init__(self, root, definitions): """ @param root: An XML root element. @type root: L{Element} @param definitions: A definitions object. @type definitions: L{Definitions} """ WObject.__init__(self, root) self.name = root.get("name") self.qname = (self.name, definitions.tns[1]) pmd = self.__metadata__.__print__ pmd.wrappers["qname"] = repr class Definitions(WObject): """ I{Root} container for all the WSDL objects defined by . @ivar id: The object id. @type id: str @ivar options: An options dictionary. @type options: L{options.Options} @ivar url: The URL used to load the object. @type url: str @ivar tns: The target namespace for the WSDL. @type tns: str @ivar schema: The collective WSDL schema object. @type schema: L{SchemaCollection} @ivar children: The raw list of child objects. @type children: [L{WObject},...] @ivar imports: The list of L{Import} children. @type imports: [L{Import},...] @ivar messages: The dictionary of L{Message} children keyed by I{qname}. @type messages: [L{Message},...] @ivar port_types: The dictionary of L{PortType} children keyed by I{qname}. @type port_types: [L{PortType},...] @ivar bindings: The dictionary of L{Binding} children keyed by I{qname}. @type bindings: [L{Binding},...] @ivar service: The service object. @type service: L{Service} """ Tag = "definitions" def __init__(self, url, options, imported_definitions=None): """ @param url: A URL to the WSDL. @type url: str @param options: An options dictionary. @type options: L{options.Options} """ log.debug("reading WSDL at: %s ...", url) reader = DocumentReader(options) d = reader.open(url) root = d.root() WObject.__init__(self, root) self.id = objid(self) self.options = options self.url = url self.tns = self.mktns(root) self.types = [] self.schema = None self.children = [] self.imports = [] self.messages = {} self.port_types = {} self.bindings = {} self.services = [] self.add_children(self.root) self.children.sort() pmd = self.__metadata__.__print__ pmd.excludes.append("children") pmd.excludes.append("wsdl") pmd.wrappers["schema"] = repr if imported_definitions is None: imported_definitions = {} imported_definitions[url] = self self.open_imports(imported_definitions) self.resolve() self.build_schema() self.set_wrapped() for s in self.services: self.add_methods(s) log.debug("WSDL at '%s' loaded:\n%s", url, self) def mktns(self, root): """Get/create the target namespace.""" tns = root.get("targetNamespace") prefix = root.findPrefix(tns) if prefix is None: log.debug("warning: tns (%s), not mapped to prefix", tns) prefix = "tns" return (prefix, tns) def add_children(self, root): """Add child objects using the factory.""" for c in root.getChildren(ns=wsdlns): child = Factory.create(c, self) if child is None: continue self.children.append(child) if isinstance(child, Import): self.imports.append(child) continue if isinstance(child, Types): self.types.append(child) continue if isinstance(child, Message): self.messages[child.qname] = child continue if isinstance(child, PortType): self.port_types[child.qname] = child continue if isinstance(child, Binding): self.bindings[child.qname] = child continue if isinstance(child, Service): self.services.append(child) continue def open_imports(self, imported_definitions): """Import the I{imported} WSDLs.""" for imp in self.imports: imp.load(self, imported_definitions) def resolve(self): """Tell all children to resolve themselves.""" for c in self.children: c.resolve(self) def build_schema(self): """Process L{Types} objects and create the schema collection.""" loaded_schemata = {} container = SchemaCollection(self) for t in (t for t in self.types if t.local()): for root in t.contents(): schema = Schema(root, self.url, self.options, loaded_schemata, container) container.add(schema) if not container: root = Element.buildPath(self.root, "types/schema") schema = Schema(root, self.url, self.options, loaded_schemata, container) container.add(schema) self.schema = container.load(self.options, loaded_schemata) #TODO: Recheck this XSD schema merging. XSD schema imports are not # supposed to be transitive. They only allow the importing schema to # reference entities from the imported schema, but do not include them # as their own content. for s in (t.schema() for t in self.types if t.imported()): self.schema.merge(s) return self.schema def add_methods(self, service): """Build method view for service.""" bindings = { "document/literal": Document(self), "rpc/literal": RPC(self), "rpc/encoded": Encoded(self)} for p in service.ports: binding = p.binding ptype = p.binding.type operations = list(p.binding.type.operations.values()) for name in (op.name for op in operations): m = Facade("Method") m.name = name m.location = p.location m.binding = Facade("binding") op = binding.operation(name) m.soap = op.soap key = "/".join((op.soap.style, op.soap.input.body.use)) m.binding.input = bindings.get(key) key = "/".join((op.soap.style, op.soap.output.body.use)) m.binding.output = bindings.get(key) p.methods[name] = m def set_wrapped(self): """Set (wrapped|bare) flag on messages.""" for b in list(self.bindings.values()): for op in list(b.operations.values()): for body in (op.soap.input.body, op.soap.output.body): body.wrapped = False if not self.options.unwrap: continue if len(body.parts) != 1: continue for p in body.parts: if p.element is None: continue query = ElementQuery(p.element) pt = query.execute(self.schema) if pt is None: raise TypeNotFound(query.ref) resolved = pt.resolve() if resolved.builtin(): continue body.wrapped = True def __getstate__(self): nopickle = ("options",) state = self.__dict__.copy() for k in nopickle: if k in state: del state[k] return state def __repr__(self): return "Definitions (id=%s)" % (self.id,) class Import(WObject): """ Represents the . @ivar location: The value of the I{location} attribute. @type location: str @ivar ns: The value of the I{namespace} attribute. @type ns: str @ivar imported: The imported object. @type imported: L{Definitions} """ def __init__(self, root, definitions): """ @param root: An XML root element. @type root: L{Element} @param definitions: A definitions object. @type definitions: L{Definitions} """ WObject.__init__(self, root) self.location = root.get("location") self.ns = root.get("namespace") self.imported = None pmd = self.__metadata__.__print__ pmd.wrappers["imported"] = repr def load(self, definitions, imported_definitions): """Load the object by opening the URL.""" url = self.location log.debug("importing (%s)", url) if "://" not in url: url = urljoin(definitions.url, url) d = imported_definitions.get(url) if not d: d = Definitions(url, definitions.options, imported_definitions) if d.root.match(Definitions.Tag, wsdlns): self.import_definitions(definitions, d) return if d.root.match(Schema.Tag, Namespace.xsdns): self.import_schema(definitions, d) return raise Exception("document at '%s' is unknown" % url) def import_definitions(self, definitions, d): """Import/merge WSDL definitions.""" definitions.types += d.types definitions.messages.update(d.messages) definitions.port_types.update(d.port_types) definitions.bindings.update(d.bindings) self.imported = d log.debug("imported (WSDL):\n%s", d) def import_schema(self, definitions, d): """Import schema as content.""" if not definitions.types: root = Element("types", ns=wsdlns) definitions.root.insert(root) types = Types(root, definitions) definitions.types.append(types) else: types = definitions.types[-1] types.root.append(d.root) log.debug("imported (XSD):\n%s", d.root) def __gt__(self, other): return False class Types(WObject): """Represents .""" def __init__(self, root, definitions): """ @param root: An XML root element. @type root: L{Element} @param definitions: A definitions object. @type definitions: L{Definitions} """ WObject.__init__(self, root) self.definitions = definitions def contents(self): return self.root.getChildren("schema", Namespace.xsdns) def schema(self): return self.definitions.schema def local(self): return self.definitions.schema is None def imported(self): return not self.local() def __gt__(self, other): return isinstance(other, Import) class Part(NamedObject): """ Represents . @ivar element: The value of the {element} attribute. Stored as a I{qref} as converted by L{suds.xsd.qualify}. @type element: str @ivar type: The value of the {type} attribute. Stored as a I{qref} as converted by L{suds.xsd.qualify}. @type type: str """ def __init__(self, root, definitions): """ @param root: An XML root element. @type root: L{Element} @param definitions: A definitions object. @type definitions: L{Definitions} """ NamedObject.__init__(self, root, definitions) pmd = Metadata() pmd.wrappers = dict(element=repr, type=repr) self.__metadata__.__print__ = pmd tns = definitions.tns self.element = self.__getref("element", tns) self.type = self.__getref("type", tns) def __getref(self, a, tns): """Get the qualified value of attribute named 'a'.""" s = self.root.get(a) if s is not None: return qualify(s, self.root, tns) class Message(NamedObject): """ Represents . @ivar parts: A list of message parts. @type parts: [I{Part},...] """ def __init__(self, root, definitions): """ @param root: An XML root element. @type root: L{Element} @param definitions: A definitions object. @type definitions: L{Definitions} """ NamedObject.__init__(self, root, definitions) self.parts = [] for p in root.getChildren("part"): part = Part(p, definitions) self.parts.append(part) def __gt__(self, other): return isinstance(other, (Import, Types)) class PortType(NamedObject): """ Represents . @ivar operations: A list of contained operations. @type operations: list """ def __init__(self, root, definitions): """ @param root: An XML root element. @type root: L{Element} @param definitions: A definitions object. @type definitions: L{Definitions} """ NamedObject.__init__(self, root, definitions) self.operations = {} for c in root.getChildren("operation"): op = Facade("Operation") op.name = c.get("name") op.tns = definitions.tns input = c.getChild("input") if input is None: op.input = None else: op.input = input.get("message") output = c.getChild("output") if output is None: op.output = None else: op.output = output.get("message") faults = [] for fault in c.getChildren("fault"): f = Facade("Fault") f.name = fault.get("name") f.message = fault.get("message") faults.append(f) op.faults = faults self.operations[op.name] = op def do_resolve(self, definitions): """ Resolve named references to other WSDL objects. @param definitions: A definitions object. @type definitions: L{Definitions} """ for op in list(self.operations.values()): if op.input is None: op.input = Message(Element("no-input"), definitions) else: qref = qualify(op.input, self.root, definitions.tns) msg = definitions.messages.get(qref) if msg is None: raise Exception("msg '%s', not-found" % (op.input,)) op.input = msg if op.output is None: op.output = Message(Element("no-output"), definitions) else: qref = qualify(op.output, self.root, definitions.tns) msg = definitions.messages.get(qref) if msg is None: raise Exception("msg '%s', not-found" % (op.output,)) op.output = msg for f in op.faults: qref = qualify(f.message, self.root, definitions.tns) msg = definitions.messages.get(qref) if msg is None: raise Exception("msg '%s', not-found" % (f.message,)) f.message = msg def operation(self, name): """ Shortcut used to get a contained operation by name. @param name: An operation name. @type name: str @return: The named operation. @rtype: Operation @raise L{MethodNotFound}: When not found. """ try: return self.operations[name] except Exception as e: raise MethodNotFound(name) def __gt__(self, other): return isinstance(other, (Import, Types, Message)) class Binding(NamedObject): """ Represents . @ivar operations: A list of contained operations. @type operations: list """ def __init__(self, root, definitions): """ @param root: An XML root element. @type root: L{Element} @param definitions: A definitions object. @type definitions: L{Definitions} """ NamedObject.__init__(self, root, definitions) self.operations = {} self.type = root.get("type") sr = self.soaproot() if sr is None: self.soap = None log.debug("binding: '%s' not a SOAP binding", self.name) return soap = Facade("soap") self.soap = soap self.soap.style = sr.get("style", default="document") self.add_operations(self.root, definitions) def soaproot(self): """Get the soap:binding.""" for ns in (soapns, soap12ns): sr = self.root.getChild("binding", ns=ns) if sr is not None: return sr def add_operations(self, root, definitions): """Add children.""" dsop = Element("operation", ns=soapns) for c in root.getChildren("operation"): op = Facade("Operation") op.name = c.get("name") sop = c.getChild("operation", default=dsop) soap = Facade("soap") soap.action = '"%s"' % (sop.get("soapAction", default=""),) soap.style = sop.get("style", default=self.soap.style) soap.input = Facade("Input") soap.input.body = Facade("Body") soap.input.headers = [] soap.output = Facade("Output") soap.output.body = Facade("Body") soap.output.headers = [] op.soap = soap input = c.getChild("input") if input is None: input = Element("input", ns=wsdlns) body = input.getChild("body") self.body(definitions, soap.input.body, body) for header in input.getChildren("header"): self.header(definitions, soap.input, header) output = c.getChild("output") if output is None: output = Element("output", ns=wsdlns) body = output.getChild("body") self.body(definitions, soap.output.body, body) for header in output.getChildren("header"): self.header(definitions, soap.output, header) faults = [] for fault in c.getChildren("fault"): sf = fault.getChild("fault") if sf is None: continue fn = fault.get("name") f = Facade("Fault") f.name = sf.get("name", default=fn) f.use = sf.get("use", default="literal") faults.append(f) soap.faults = faults self.operations[op.name] = op def body(self, definitions, body, root): """Add the input/output body properties.""" if root is None: body.use = "literal" body.namespace = definitions.tns body.parts = () return parts = root.get("parts") if parts is None: body.parts = () else: body.parts = re.split("[\\s,]", parts) body.use = root.get("use", default="literal") ns = root.get("namespace") if ns is None: body.namespace = definitions.tns else: prefix = root.findPrefix(ns, "b0") body.namespace = (prefix, ns) def header(self, definitions, parent, root): """Add the input/output header properties.""" if root is None: return header = Facade("Header") parent.headers.append(header) header.use = root.get("use", default="literal") ns = root.get("namespace") if ns is None: header.namespace = definitions.tns else: prefix = root.findPrefix(ns, "h0") header.namespace = (prefix, ns) msg = root.get("message") if msg is not None: header.message = msg part = root.get("part") if part is not None: header.part = part def do_resolve(self, definitions): """ Resolve named references to other WSDL objects. This includes cross-linking information (from) the portType (to) the I{SOAP} protocol information on the binding for each operation. @param definitions: A definitions object. @type definitions: L{Definitions} """ self.__resolveport(definitions) for op in list(self.operations.values()): self.__resolvesoapbody(definitions, op) self.__resolveheaders(definitions, op) self.__resolvefaults(definitions, op) def __resolveport(self, definitions): """ Resolve port_type reference. @param definitions: A definitions object. @type definitions: L{Definitions} """ ref = qualify(self.type, self.root, definitions.tns) port_type = definitions.port_types.get(ref) if port_type is None: raise Exception("portType '%s', not-found" % (self.type,)) # Later on we will require access to the message data referenced by # this port_type instance, and in order for those data references to be # available, port_type first needs to dereference its message # identification string. The only scenario where the port_type might # possibly not have already resolved its references, and where this # explicit resolve() call is required, is if we are dealing with a # recursive WSDL import chain. port_type.resolve(definitions) self.type = port_type def __resolvesoapbody(self, definitions, op): """ Resolve SOAP body I{message} parts by cross-referencing with operation defined in port type. @param definitions: A definitions object. @type definitions: L{Definitions} @param op: An I{operation} object. @type op: I{operation} """ ptop = self.type.operation(op.name) if ptop is None: raise Exception("operation '%s' not defined in portType" % ( op.name,)) soap = op.soap parts = soap.input.body.parts if parts: pts = [] for p in ptop.input.parts: if p.name in parts: pts.append(p) soap.input.body.parts = pts else: soap.input.body.parts = ptop.input.parts parts = soap.output.body.parts if parts: pts = [] for p in ptop.output.parts: if p.name in parts: pts.append(p) soap.output.body.parts = pts else: soap.output.body.parts = ptop.output.parts def __resolveheaders(self, definitions, op): """ Resolve SOAP header I{message} references. @param definitions: A definitions object. @type definitions: L{Definitions} @param op: An I{operation} object. @type op: I{operation} """ soap = op.soap headers = soap.input.headers + soap.output.headers for header in headers: mn = header.message ref = qualify(mn, self.root, definitions.tns) message = definitions.messages.get(ref) if message is None: raise Exception("message '%s', not-found" % (mn,)) pn = header.part for p in message.parts: if p.name == pn: header.part = p break if pn == header.part: raise Exception("message '%s' has not part named '%s'" % ( ref, pn)) def __resolvefaults(self, definitions, op): """ Resolve SOAP fault I{message} references by cross-referencing with operations defined in the port type. @param definitions: A definitions object. @type definitions: L{Definitions} @param op: An I{operation} object. @type op: I{operation} """ ptop = self.type.operation(op.name) if ptop is None: raise Exception("operation '%s' not defined in portType" % ( op.name,)) soap = op.soap for fault in soap.faults: for f in ptop.faults: if f.name == fault.name: fault.parts = f.message.parts continue if hasattr(fault, "parts"): continue raise Exception("fault '%s' not defined in portType '%s'" % ( fault.name, self.type.name)) def operation(self, name): """ Shortcut used to get a contained operation by name. @param name: An operation name. @type name: str @return: The named operation. @rtype: Operation @raise L{MethodNotFound}: When not found. """ try: return self.operations[name] except Exception: raise MethodNotFound(name) def __gt__(self, other): return not isinstance(other, Service) class Port(NamedObject): """ Represents a service port. @ivar service: A service. @type service: L{Service} @ivar binding: A binding name. @type binding: str @ivar location: The service location (URL). @type location: str """ def __init__(self, root, definitions, service): """ @param root: An XML root element. @type root: L{Element} @param definitions: A definitions object. @type definitions: L{Definitions} @param service: A service object. @type service: L{Service} """ NamedObject.__init__(self, root, definitions) self.__service = service self.binding = root.get("binding") address = root.getChild("address") self.location = address is not None and address.get("location") self.methods = {} def method(self, name): """ Get a method defined in this portType by name. @param name: A method name. @type name: str @return: The requested method object. @rtype: I{Method} """ return self.methods.get(name) class Service(NamedObject): """ Represents . @ivar port: The contained ports. @type port: [Port,..] @ivar methods: The contained methods for all ports. @type methods: [Method,..] """ def __init__(self, root, definitions): """ @param root: An XML root element. @type root: L{Element} @param definitions: A definitions object. @type definitions: L{Definitions} """ NamedObject.__init__(self, root, definitions) self.ports = [] for p in root.getChildren("port"): port = Port(p, definitions, self) self.ports.append(port) def port(self, name): """ Locate a port by name. @param name: A port name. @type name: str @return: The port object. @rtype: L{Port} """ for p in self.ports: if p.name == name: return p def setlocation(self, url, names=None): """ Override the invocation location (URL) for service method. @param url: A URL location. @type url: A URL. @param names: A list of method names. None=ALL @type names: [str,..] """ for p in self.ports: for m in list(p.methods.values()): if names is None or m.name in names: m.location = url def do_resolve(self, definitions): """ Resolve named references to other WSDL objects. Ports without SOAP bindings are discarded. @param definitions: A definitions object. @type definitions: L{Definitions} """ filtered = [] for p in self.ports: ref = qualify(p.binding, self.root, definitions.tns) binding = definitions.bindings.get(ref) if binding is None: raise Exception("binding '%s', not-found" % (p.binding,)) if binding.soap is None: log.debug("binding '%s' - not a SOAP binding, discarded", binding.name) continue # After we have been resolved, our caller will expect that the # binding we are referencing has been fully constructed, i.e. # resolved, as well. The only scenario where the operations binding # might possibly not have already resolved its references, and # where this explicit resolve() call is required, is if we are # dealing with a recursive WSDL import chain. binding.resolve(definitions) p.binding = binding filtered.append(p) self.ports = filtered def __gt__(self, other): return True class Factory: """ Simple WSDL object factory. @cvar tags: Dictionary of tag-->constructor mappings. @type tags: dict """ tags = { "import": Import, "types": Types, "message": Message, "portType": PortType, "binding": Binding, "service": Service} @classmethod def create(cls, root, definitions): """ Create an object based on the root tag name. @param root: An XML root element. @type root: L{Element} @param definitions: A definitions object. @type definitions: L{Definitions} @return: The created object. @rtype: L{WObject} """ fn = cls.tags.get(root.name) if fn is not None: return fn(root, definitions) suds-1.1.2/suds/wsse.py000066400000000000000000000161421425611400200150010ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ The I{wsse} module provides WS-Security. """ from logging import getLogger from suds import * from suds.sudsobject import Object from suds.sax.element import Element from suds.sax.date import DateTime, UtcTimezone from datetime import datetime, timedelta try: from hashlib import md5 except ImportError: # Python 2.4 compatibility from md5 import md5 dsns = \ ('ds', 'http://www.w3.org/2000/09/xmldsig#') wssens = \ ('wsse', 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd') wsuns = \ ('wsu', 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd') wsencns = \ ('wsenc', 'http://www.w3.org/2001/04/xmlenc#') nonce_encoding_type = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" username_token_profile = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0" wsdigest = "%s#PasswordDigest" % username_token_profile wstext = "%s#PasswordText" % username_token_profile class Security(Object): """ WS-Security object. @ivar tokens: A list of security tokens @type tokens: [L{Token},...] @ivar signatures: A list of signatures. @type signatures: TBD @ivar references: A list of references. @type references: TBD @ivar keys: A list of encryption keys. @type keys: TBD """ def __init__(self): """ """ Object.__init__(self) self.mustUnderstand = True self.tokens = [] self.signatures = [] self.references = [] self.keys = [] def xml(self): """ Get xml representation of the object. @return: The root node. @rtype: L{Element} """ root = Element('Security', ns=wssens) root.set('mustUnderstand', str(self.mustUnderstand).lower()) for t in self.tokens: root.append(t.xml()) return root class Token(Object): """ I{Abstract} security token. """ @classmethod def now(cls): return datetime.now() @classmethod def utc(cls): return datetime.utcnow().replace(tzinfo=UtcTimezone()) @classmethod def sysdate(cls): utc = DateTime(cls.utc()) return str(utc) def __init__(self): Object.__init__(self) class UsernameToken(Token): """ Represents a basic I{UsernameToken} WS-Secuirty token. @ivar username: A username. @type username: str @ivar password: A password. @type password: str @type password_digest: A password digest @ivar nonce: A set of bytes to prevent replay attacks. @type nonce: str @ivar created: The token created. @type created: L{datetime} """ def __init__(self, username=None, password=None): """ @param username: A username. @type username: str @param password: A password. @type password: str """ Token.__init__(self) self.username = username self.password = password self.nonce = None self.created = None self.password_digest = None self.nonce_has_encoding = False def setnonceencoding(self, value=False): self.nonce_has_encoding = value def setpassworddigest(self, passwd_digest): """ Set password digest which is a text returned by auth WS. """ self.password_digest = passwd_digest def setnonce(self, text=None): """ Set I{nonce} which is an arbitrary set of bytes to prevent replay attacks. @param text: The nonce text value. Generated when I{None}. @type text: str """ if text is None: s = [] s.append(self.username) s.append(self.password) s.append(Token.sysdate()) try: # FIPS requires usedforsecurity=False and might not be # available on all distros: https://bugs.python.org/issue9216 m = md5(usedforsecurity=False) except (AttributeError, TypeError): m = md5() m.update(':'.join(s).encode('utf-8')) self.nonce = m.hexdigest() else: self.nonce = text def setcreated(self, dt=None): """ Set I{created}. @param dt: The created date & time. Set as datetime.utc() when I{None}. @type dt: L{datetime} """ if dt is None: self.created = Token.utc() else: self.created = dt def xml(self): """ Get xml representation of the object. @return: The root node. @rtype: L{Element} """ root = Element('UsernameToken', ns=wssens) u = Element('Username', ns=wssens) u.setText(self.username) root.append(u) p = Element('Password', ns=wssens) p.setText(self.password) if self.password_digest: p.set("Type", wsdigest) p.setText(self.password_digest) else: p.set("Type", wstext) root.append(p) if self.nonce is not None: n = Element('Nonce', ns=wssens) if self.nonce_has_encoding: n.set("EncodingType", nonce_encoding_type) n.setText(self.nonce) root.append(n) if self.created is not None: n = Element('Created', ns=wsuns) n.setText(str(DateTime(self.created))) root.append(n) return root class Timestamp(Token): """ Represents the I{Timestamp} WS-Secuirty token. @ivar created: The token created. @type created: L{datetime} @ivar expires: The token expires. @type expires: L{datetime} """ def __init__(self, validity=90): """ @param validity: The time in seconds. @type validity: int """ Token.__init__(self) self.created = Token.utc() self.expires = self.created + timedelta(seconds=validity) def xml(self): root = Element("Timestamp", ns=wsuns) created = Element('Created', ns=wsuns) created.setText(str(DateTime(self.created))) expires = Element('Expires', ns=wsuns) expires.setText(str(DateTime(self.expires))) root.append(created) root.append(expires) return root suds-1.1.2/suds/xsd/000077500000000000000000000000001425611400200142405ustar00rootroot00000000000000suds-1.1.2/suds/xsd/__init__.py000066400000000000000000000050431425611400200163530ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) from suds import * from suds.sax import Namespace, splitPrefix def qualify(ref, resolvers, defns=Namespace.default): """ Get a reference that is I{qualified} by namespace. @param ref: A referenced schema type name. @type ref: str @param resolvers: A list of objects to be used to resolve types. @type resolvers: [L{sax.element.Element},] @param defns: An optional target namespace used to qualify references when no prefix is specified. @type defns: A default namespace I{tuple: (prefix,uri)} used when ref not prefixed. @return: A qualified reference. @rtype: (name, namespace-uri) """ ns = None p, n = splitPrefix(ref) if p is not None: if not isinstance(resolvers, (list, tuple)): resolvers = (resolvers,) for r in resolvers: resolved = r.resolvePrefix(p) if resolved[1] is not None: ns = resolved break if ns is None: raise Exception('prefix (%s) not resolved' % p) else: ns = defns return (n, ns[1]) def isqref(object): """ Get whether the object is a I{qualified reference}. @param object: An object to be tested. @type object: I{any} @rtype: boolean @see: L{qualify} """ return (\ isinstance(object, tuple) and \ len(object) == 2 and \ isinstance(object[0], str) and \ isinstance(object[1], str)) class Filter: def __init__(self, inclusive=False, *items): self.inclusive = inclusive self.items = items def __contains__(self, x): if self.inclusive: result = ( x in self.items ) else: result = ( x not in self.items ) return result suds-1.1.2/suds/xsd/depsort.py000066400000000000000000000047741425611400200163060ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Dependency/topological sort implementation. """ from suds import * from logging import getLogger log = getLogger(__name__) def dependency_sort(dependency_tree): """ Sorts items 'dependencies first' in a given dependency tree. A dependency tree is a dictionary mapping an object to a collection its dependency objects. Result is a properly sorted list of items, where each item is a 2-tuple containing an object and its dependency list, as given in the input dependency tree. If B is directly or indirectly dependent on A and they are not both a part of the same dependency cycle (i.e. then A is neither directly nor indirectly dependent on B) then A needs to come before B. If A and B are a part of the same dependency cycle, i.e. if they are both directly or indirectly dependent on each other, then it does not matter which comes first. Any entries found listed as dependencies, but that do not have their own dependencies listed as well, are logged & ignored. @return: The sorted items. @rtype: list """ sorted = [] processed = set() for key, deps in dependency_tree.items(): _sort_r(sorted, processed, key, deps, dependency_tree) return sorted def _sort_r(sorted, processed, key, deps, dependency_tree): """Recursive topological sort implementation.""" if key in processed: return processed.add(key) for dep_key in deps: dep_deps = dependency_tree.get(dep_key) if dep_deps is None: log.debug('"%s" not found, skipped', Repr(dep_key)) continue _sort_r(sorted, processed, dep_key, dep_deps, dependency_tree) sorted.append((key, deps)) suds-1.1.2/suds/xsd/doctor.py000066400000000000000000000140451425611400200161100ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ The I{doctor} module provides classes for fixing broken (sick) schema(s). """ from suds.sax import Namespace from suds.sax.element import Element from suds.plugin import DocumentPlugin, DocumentContext from logging import getLogger log = getLogger(__name__) class Doctor: """ Schema Doctor. """ def examine(self, root): """ Examine and repair the schema (if necessary). @param root: A schema root element. @type root: L{Element} """ pass class Practice(Doctor): """ A collection of doctors. @ivar doctors: A list of doctors. @type doctors: list """ def __init__(self): self.doctors = [] def add(self, doctor): """ Add a doctor to the practice @param doctor: A doctor to add. @type doctor: L{Doctor} """ self.doctors.append(doctor) def examine(self, root): for d in self.doctors: d.examine(root) return root class TnsFilter: """ Target Namespace filter. @ivar tns: A list of target namespaces. @type tns: [str,...] """ def __init__(self, *tns): """ @param tns: A list of target namespaces. @type tns: [str,...] """ self.tns = [] self.add(*tns) def add(self, *tns): """ Add I{targetNamespaces} to be added. @param tns: A list of target namespaces. @type tns: [str,...] """ self.tns += tns def match(self, root, ns): """ Match by I{targetNamespace} excluding those that are equal to the specified namespace to prevent adding an import to itself. @param root: A schema root. @type root: L{Element} """ tns = root.get('targetNamespace') if len(self.tns): matched = ( tns in self.tns ) else: matched = 1 itself = ( ns == tns ) return ( matched and not itself ) class Import: """ An to be applied. @cvar xsdns: The XSD namespace. @type xsdns: (p,u) @ivar ns: An import namespace. @type ns: str @ivar location: An optional I{schemaLocation}. @type location: str @ivar filter: A filter used to restrict application to a particular schema. @type filter: L{TnsFilter} """ xsdns = Namespace.xsdns def __init__(self, ns, location=None): """ @param ns: An import namespace. @type ns: str @param location: An optional I{schemaLocation}. @type location: str """ self.ns = ns self.location = location self.filter = TnsFilter() def setfilter(self, filter): """ Set the filter. @param filter: A filter to set. @type filter: L{TnsFilter} """ self.filter = filter def apply(self, root): """ Apply the import (rule) to the specified schema. If the schema does not already contain an import for the I{namespace} specified here, it is added. @param root: A schema root. @type root: L{Element} """ if not self.filter.match(root, self.ns): return if self.exists(root): return node = Element('import', ns=self.xsdns) node.set('namespace', self.ns) if self.location is not None: node.set('schemaLocation', self.location) log.debug('inserting: %s', node) root.insert(node) def add(self, root): """ Add an to the specified schema root. @param root: A schema root. @type root: L{Element} """ node = Element('import', ns=self.xsdns) node.set('namespace', self.ns) if self.location is not None: node.set('schemaLocation', self.location) log.debug('%s inserted', node) root.insert(node) def exists(self, root): """ Check to see if the already exists in the specified schema root by matching I{namespace}. @param root: A schema root. @type root: L{Element} """ for node in root.children: if node.name != 'import': continue ns = node.get('namespace') if self.ns == ns: return 1 return 0 class ImportDoctor(Doctor, DocumentPlugin): """ Doctor used to fix missing imports. @ivar imports: A list of imports to apply. @type imports: [L{Import},...] """ def __init__(self, *imports): self.imports = [] self.add(*imports) def add(self, *imports): """ Add a namespace to be checked. @param imports: A list of L{Import} objects. @type imports: [L{Import},..] """ self.imports += imports def examine(self, node): for imp in self.imports: imp.apply(node) def parsed(self, context): node = context.document # xsd root if node.name == 'schema' and Namespace.xsd(node.namespace()): self.examine(node) return # look deeper context = DocumentContext() for child in node: context.document = child self.parsed(context) suds-1.1.2/suds/xsd/query.py000066400000000000000000000143411425611400200157620ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ The I{query} module defines a class for performing schema queries. """ from suds import * from suds.sudsobject import * from suds.xsd import qualify, isqref from suds.xsd.sxbuiltin import Factory from logging import getLogger log = getLogger(__name__) class Query(Object): """ Schema query base class. """ def __init__(self, ref=None): """ @param ref: The schema reference being queried. @type ref: qref """ Object.__init__(self) self.id = objid(self) self.ref = ref self.history = [] self.resolved = False if not isqref(self.ref): raise Exception('%s, must be qref' % tostr(self.ref)) def execute(self, schema): """ Execute this query using the specified schema. @param schema: The schema associated with the query. The schema is used by the query to search for items. @type schema: L{schema.Schema} @return: The item matching the search criteria. @rtype: L{sxbase.SchemaObject} """ raise Exception('not-implemented by subclass') def filter(self, result): """ Filter the specified result based on query criteria. @param result: A potential result. @type result: L{sxbase.SchemaObject} @return: True if result should be excluded. @rtype: boolean """ if result is None: return True reject = ( result in self.history ) if reject: log.debug('result %s, rejected by\n%s', Repr(result), self) return reject def result(self, result): """ Query result post processing. @param result: A query result. @type result: L{sxbase.SchemaObject} """ if result is None: log.debug('%s, not-found', self.ref) return if self.resolved: result = result.resolve() log.debug('%s, found as: %s', self.ref, Repr(result)) self.history.append(result) return result class BlindQuery(Query): """ Schema query class that I{blindly} searches for a reference in the specified schema. It may be used to find Elements and Types but will match on an Element first. This query will also find builtins. """ def execute(self, schema): if schema.builtin(self.ref): name = self.ref[0] b = Factory.create(schema, name) log.debug('%s, found builtin (%s)', self.id, name) return b result = None for d in (schema.elements, schema.types): result = d.get(self.ref) if self.filter(result): result = None else: break if result is None: eq = ElementQuery(self.ref) eq.history = self.history result = eq.execute(schema) return self.result(result) class TypeQuery(Query): """ Schema query class that searches for Type references in the specified schema. Matches on root types only. """ def execute(self, schema): if schema.builtin(self.ref): name = self.ref[0] b = Factory.create(schema, name) log.debug('%s, found builtin (%s)', self.id, name) return b result = schema.types.get(self.ref) if self.filter(result): result = None return self.result(result) class GroupQuery(Query): """ Schema query class that searches for Group references in the specified schema. """ def execute(self, schema): result = schema.groups.get(self.ref) if self.filter(result): result = None return self.result(result) class AttrQuery(Query): """ Schema query class that searches for Attribute references in the specified schema. Matches on root Attribute by qname first, then searches deeper into the document. """ def execute(self, schema): result = schema.attributes.get(self.ref) if self.filter(result): result = self.__deepsearch(schema) return self.result(result) def __deepsearch(self, schema): from suds.xsd.sxbasic import Attribute result = None for e in schema.all: result = e.find(self.ref, (Attribute,)) if self.filter(result): result = None else: break return result class AttrGroupQuery(Query): """ Schema query class that searches for attributeGroup references in the specified schema. """ def execute(self, schema): result = schema.agrps.get(self.ref) if self.filter(result): result = None return self.result(result) class ElementQuery(Query): """ Schema query class that searches for Element references in the specified schema. Matches on root Elements by qname first, then searches deeper into the document. """ def execute(self, schema): result = schema.elements.get(self.ref) if self.filter(result): result = self.__deepsearch(schema) return self.result(result) def __deepsearch(self, schema): from suds.xsd.sxbasic import Element result = None for e in schema.all: result = e.find(self.ref, (Element,)) if self.filter(result): result = None else: break return result suds-1.1.2/suds/xsd/schema.py000066400000000000000000000371341425611400200160620ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ The I{schema} module provides an intelligent representation of an XSD schema. The I{raw} model is the XML tree and the I{model} is a denormalized, objectified and intelligent view of the schema. Most of the I{value-add} provided by the model is centered around transparent referenced type resolution and targeted denormalization. """ from suds import * from suds.xsd import * from suds.xsd.depsort import dependency_sort from suds.xsd.sxbuiltin import * from suds.xsd.sxbase import SchemaObject from suds.xsd.sxbasic import Factory as BasicFactory from suds.xsd.sxbuiltin import Factory as BuiltinFactory from suds.sax import splitPrefix, Namespace from suds.sax.element import Element from logging import getLogger log = getLogger(__name__) class SchemaCollection(UnicodeMixin): """ A collection of schema objects. This class is needed because a WSDL may contain more then one node. @ivar wsdl: A WSDL object. @type wsdl: L{suds.wsdl.Definitions} @ivar children: A list contained schemas. @type children: [L{Schema},...] @ivar namespaces: A dictionary of contained schemas by namespace. @type namespaces: {str: L{Schema}} """ def __init__(self, wsdl): """ @param wsdl: A WSDL object. @type wsdl: L{suds.wsdl.Definitions} """ self.wsdl = wsdl self.children = [] self.namespaces = {} def add(self, schema): """ Add a schema node to the collection. Schema(s) within the same target namespace are consolidated. @param schema: A schema object. @type schema: (L{Schema}) """ key = schema.tns[1] existing = self.namespaces.get(key) if existing is None: self.children.append(schema) self.namespaces[key] = schema else: existing.root.children += schema.root.children existing.root.nsprefixes.update(schema.root.nsprefixes) def load(self, options, loaded_schemata): """ Load schema objects for the root nodes. - de-reference schemas - merge schemas @param options: An options dictionary. @type options: L{options.Options} @param loaded_schemata: Already loaded schemata cache (URL --> Schema). @type loaded_schemata: dict @return: The merged schema. @rtype: L{Schema} """ if options.autoblend: self.autoblend() for child in self.children: child.build() for child in self.children: child.open_imports(options, loaded_schemata) for child in self.children: child.dereference() log.debug("loaded:\n%s", self) merged = self.merge() log.debug("MERGED:\n%s", merged) return merged def autoblend(self): """ Ensure that all schemas within the collection import each other which has a blending effect. @return: self @rtype: L{SchemaCollection} """ namespaces = list(self.namespaces.keys()) for s in self.children: for ns in namespaces: tns = s.root.get("targetNamespace") if tns == ns: continue for imp in s.root.getChildren("import"): if imp.get("namespace") == ns: continue imp = Element("import", ns=Namespace.xsdns) imp.set("namespace", ns) s.root.append(imp) return self def locate(self, ns): """ Find a schema by namespace. Only the URI portion of the namespace is compared to each schema's I{targetNamespace}. @param ns: A namespace. @type ns: (prefix, URI) @return: The schema matching the namespace, else None. @rtype: L{Schema} """ return self.namespaces.get(ns[1]) def merge(self): """ Merge contained schemas into one. @return: The merged schema. @rtype: L{Schema} """ if self.children: schema = self.children[0] for s in self.children[1:]: schema.merge(s) return schema def __len__(self): return len(self.children) def __unicode__(self): result = ["\nschema collection"] for s in self.children: result.append(s.str(1)) return "\n".join(result) class Schema(UnicodeMixin): """ The schema is an objectification of a (XSD) definition. It provides inspection, lookup and type resolution. @ivar root: The root node. @type root: L{sax.element.Element} @ivar baseurl: The I{base} URL for this schema. @type baseurl: str @ivar container: A schema collection containing this schema. @type container: L{SchemaCollection} @ivar children: A list of direct top level children. @type children: [L{SchemaObject},...] @ivar all: A list of all (includes imported) top level children. @type all: [L{SchemaObject},...] @ivar types: A schema types cache. @type types: {name:L{SchemaObject}} @ivar imports: A list of import objects. @type imports: [L{SchemaObject},...] @ivar elements: A list of objects. @type elements: [L{SchemaObject},...] @ivar attributes: A list of objects. @type attributes: [L{SchemaObject},...] @ivar groups: A list of group objects. @type groups: [L{SchemaObject},...] @ivar agrps: A list of attribute group objects. @type agrps: [L{SchemaObject},...] @ivar form_qualified: The flag indicating: (@elementFormDefault). @type form_qualified: bool """ Tag = "schema" def __init__(self, root, baseurl, options, loaded_schemata=None, container=None): """ @param root: The XML root. @type root: L{sax.element.Element} @param baseurl: The base URL used for importing. @type baseurl: basestring @param options: An options dictionary. @type options: L{options.Options} @param loaded_schemata: An optional already loaded schemata cache (URL --> Schema). @type loaded_schemata: dict @param container: An optional container. @type container: L{SchemaCollection} """ self.root = root self.id = objid(self) self.tns = self.mktns() self.baseurl = baseurl self.container = container self.children = [] self.all = [] self.types = {} self.imports = [] self.elements = {} self.attributes = {} self.groups = {} self.agrps = {} if options.doctor is not None: options.doctor.examine(root) form = self.root.get("elementFormDefault") self.form_qualified = form == "qualified" # If we have a container, that container is going to take care of # finishing our build for us in parallel with building all the other # schemata in that container. That allows the different schema within # the same container to freely reference each other. #TODO: check whether this container content build parallelization is # really necessary or if we can simply build our top-level WSDL # contained schemata one by one as they are loaded if container is None: if loaded_schemata is None: loaded_schemata = {} loaded_schemata[baseurl] = self #TODO: It seems like this build() step can be done for each schema # on its own instead of letting the container do it. Building our # XSD schema objects should not require any external schema # information and even references between XSD schema objects within # the same schema can not be established until all the XSD schema # objects have been built. The only reason I can see right now why # this step has been placed under container control is so our # container (a SchemaCollection instance) can add some additional # XML elements to our schema before our XSD schema object entities # get built, but there is bound to be a cleaner way to do this, # similar to how we support such XML modifications in suds plugins. self.build() self.open_imports(options, loaded_schemata) log.debug("built:\n%s", self) self.dereference() log.debug("dereferenced:\n%s", self) def mktns(self): """ Make the schema's target namespace. @return: namespace representation of the schema's targetNamespace value. @rtype: (prefix, URI) """ tns = self.root.get("targetNamespace") tns_prefix = None if tns is not None: tns_prefix = self.root.findPrefix(tns) return tns_prefix, tns def build(self): """ Build the schema (object graph) using the root node using the factory. - Build the graph. - Collate the children. """ self.children = BasicFactory.build(self.root, self) collated = BasicFactory.collate(self.children) self.children = collated[0] self.attributes = collated[2] self.imports = collated[1] self.elements = collated[3] self.types = collated[4] self.groups = collated[5] self.agrps = collated[6] def merge(self, schema): """ Merge the schema contents. Only objects not already contained in this schema's collections are merged. This provides support for bidirectional imports producing cyclic includes. @returns: self @rtype: L{Schema} """ for item in list(schema.attributes.items()): if item[0] in self.attributes: continue self.all.append(item[1]) self.attributes[item[0]] = item[1] for item in list(schema.elements.items()): if item[0] in self.elements: continue self.all.append(item[1]) self.elements[item[0]] = item[1] for item in list(schema.types.items()): if item[0] in self.types: continue self.all.append(item[1]) self.types[item[0]] = item[1] for item in list(schema.groups.items()): if item[0] in self.groups: continue self.all.append(item[1]) self.groups[item[0]] = item[1] for item in list(schema.agrps.items()): if item[0] in self.agrps: continue self.all.append(item[1]) self.agrps[item[0]] = item[1] schema.merged = True return self def open_imports(self, options, loaded_schemata): """ Instruct all contained L{sxbasic.Import} children to import all of their referenced schemas. The imported schema contents are I{merged} in. @param options: An options dictionary. @type options: L{options.Options} @param loaded_schemata: Already loaded schemata cache (URL --> Schema). @type loaded_schemata: dict """ for imp in self.imports: imported = imp.open(options, loaded_schemata) if imported is None: continue imported.open_imports(options, loaded_schemata) log.debug("imported:\n%s", imported) self.merge(imported) def dereference(self): """Instruct all children to perform dereferencing.""" all = [] indexes = {} for child in self.children: child.content(all) dependencies = {} for x in all: x.qualify() midx, deps = x.dependencies() dependencies[x] = deps indexes[x] = midx for x, deps in dependency_sort(dependencies): midx = indexes.get(x) if midx is None: continue d = deps[midx] log.debug("(%s) merging %s <== %s", self.tns[1], Repr(x), Repr(d)) x.merge(d) def locate(self, ns): """ Find a schema by namespace. Only the URI portion of the namespace is compared to each schema's I{targetNamespace}. The request is passed on to the container. @param ns: A namespace. @type ns: (prefix, URI) @return: The schema matching the namespace, else None. @rtype: L{Schema} """ if self.container is not None: return self.container.locate(ns) def custom(self, ref, context=None): """ Get whether the specified reference is B{not} an (xs) builtin. @param ref: A str or qref. @type ref: (str|qref) @return: True if B{not} a builtin, else False. @rtype: bool """ return ref is None or not self.builtin(ref, context) def builtin(self, ref, context=None): """ Get whether the specified reference is an (xs) builtin. @param ref: A str or qref. @type ref: (str|qref) @return: True if builtin, else False. @rtype: bool """ w3 = "http://www.w3.org" try: if isqref(ref): ns = ref[1] return ref[0] in Factory.tags and ns.startswith(w3) if context is None: context = self.root prefix = splitPrefix(ref)[0] prefixes = context.findPrefixes(w3, "startswith") return prefix in prefixes and ref[0] in Factory.tags except Exception: return False def instance(self, root, baseurl, loaded_schemata, options): """ Create and return an new schema object using the specified I{root} and I{URL}. @param root: A schema root node. @type root: L{sax.element.Element} @param baseurl: A base URL. @type baseurl: str @param loaded_schemata: Already loaded schemata cache (URL --> Schema). @type loaded_schemata: dict @param options: An options dictionary. @type options: L{options.Options} @return: The newly created schema object. @rtype: L{Schema} @note: This is only used by Import children. """ return Schema(root, baseurl, options, loaded_schemata) def str(self, indent=0): tab = "%*s" % (indent * 3, "") result = [] result.append("%s%s" % (tab, self.id)) result.append("%s(raw)" % (tab,)) result.append(self.root.str(indent + 1)) result.append("%s(model)" % (tab,)) for c in self.children: result.append(c.str(indent + 1)) result.append("") return "\n".join(result) def __repr__(self): return '<%s tns="%s"/>' % (self.id, self.tns[1]) def __unicode__(self): return self.str() suds-1.1.2/suds/xsd/sxbase.py000066400000000000000000000477771425611400200161250ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """I{Base} classes representing XSD schema objects.""" from suds import * from suds.xsd import * from suds.sax.element import Element from suds.sax import Namespace from logging import getLogger log = getLogger(__name__) class SchemaObject(UnicodeMixin): """ A schema object is an extension to object with schema awareness. @ivar root: The XML root element. @type root: L{Element} @ivar schema: The schema containing this object. @type schema: L{schema.Schema} @ivar form_qualified: A flag indicating that @elementFormDefault has a value of I{qualified}. @type form_qualified: boolean @ivar nillable: A flag indicating that @nillable has a value of I{true}. @type nillable: boolean @ivar default: The default value. @type default: object @ivar rawchildren: A list raw of all children. @type rawchildren: [L{SchemaObject},...] """ @classmethod def prepend(cls, d, s, filter=Filter()): """ Prepend B{s}ource XSD schema objects to the B{d}estination list. B{filter} is used to decide which objects to prepend and which to skip. @param d: The destination list. @type d: list @param s: The source list. @type s: list @param filter: A filter allowing items to be prepended. @type filter: L{Filter} """ i = 0 for x in s: if x in filter: d.insert(i, x) i += 1 @classmethod def append(cls, d, s, filter=Filter()): """ Append B{s}ource XSD schema objects to the B{d}estination list. B{filter} is used to decide which objects to append and which to skip. @param d: The destination list. @type d: list @param s: The source list. @type s: list @param filter: A filter that allows items to be appended. @type filter: L{Filter} """ for item in s: if item in filter: d.append(item) def __init__(self, schema, root): """ @param schema: The containing schema. @type schema: L{schema.Schema} @param root: The XML root node. @type root: L{Element} """ self.schema = schema self.root = root self.id = objid(self) self.name = root.get("name") self.qname = (self.name, schema.tns[1]) self.min = root.get("minOccurs") self.max = root.get("maxOccurs") self.type = root.get("type") self.ref = root.get("ref") self.form_qualified = schema.form_qualified self.nillable = False self.default = root.get("default") self.rawchildren = [] def attributes(self, filter=Filter()): """ Get only the attribute content. @param filter: A filter to constrain the result. @type filter: L{Filter} @return: A list of (attr, ancestry) tuples. @rtype: [(L{SchemaObject}, [L{SchemaObject},..]),..] """ result = [] for child, ancestry in self: if child.isattr() and child in filter: result.append((child, ancestry)) return result def children(self, filter=Filter()): """ Get only the I{direct} or non-attribute content. @param filter: A filter to constrain the result. @type filter: L{Filter} @return: A list tuples: (child, ancestry) @rtype: [(L{SchemaObject}, [L{SchemaObject},..]),..] """ result = [] for child, ancestry in self: if not child.isattr() and child in filter: result.append((child, ancestry)) return result def get_attribute(self, name): """ Get (find) an attribute by name. @param name: A attribute name. @type name: str @return: A tuple: the requested (attribute, ancestry). @rtype: (L{SchemaObject}, [L{SchemaObject},..]) """ for child, ancestry in self.attributes(): if child.name == name: return child, ancestry return None, [] def get_child(self, name): """ Get (find) a I{non-attribute} child by name. @param name: A child name. @type name: str @return: A tuple: the requested (child, ancestry). @rtype: (L{SchemaObject}, [L{SchemaObject},..]) """ for child, ancestry in self.children(): if child.any() or child.name == name: return child, ancestry return None, [] def namespace(self, prefix=None): """ Get this property's namespace. @param prefix: The default prefix. @type prefix: str @return: The schema's target namespace. @rtype: (I{prefix}, I{URI}) """ ns = self.schema.tns if ns[0] is None: ns = (prefix, ns[1]) return ns def default_namespace(self): return self.root.defaultNamespace() def multi_occurrence(self): """ Get whether the node has multiple occurrences, i.e. is a I{collection}. @return: True if it has, False if it has at most 1 occurrence. @rtype: boolean """ max = self.max if max is None: return False if max.isdigit(): return int(max) > 1 return max == "unbounded" def optional(self): """ Get whether this type is optional. @return: True if optional, else False. @rtype: boolean """ return self.min == "0" def required(self): """ Get whether this type is required. @return: True if required, else False. @rtype: boolean """ return not self.optional() def resolve(self, nobuiltin=False): """ Resolve the node's type reference and return the referenced type node. Only XSD schema objects that actually support 'having a type' custom implement this interface while others simply resolve as themselves. @param nobuiltin: Flag indicating whether resolving to an external XSD built-in type should not be allowed. @return: The resolved (true) type. @rtype: L{SchemaObject} """ return self def sequence(self): """ Get whether this is an . @return: True if , else False. @rtype: boolean """ return False def xslist(self): """ Get whether this is an . @return: True if , else False. @rtype: boolean """ return False def all(self): """ Get whether this is an . @return: True if , else False. @rtype: boolean """ return False def choice(self): """ Get whether this is an . @return: True if , else False. @rtype: boolean """ return False def any(self): """ Get whether this is an . @return: True if , else False. @rtype: boolean """ return False def builtin(self): """ Get whether this is a built-in schema-instance XSD type. @return: True if a built-in type, else False. @rtype: boolean """ return False def enum(self): """ Get whether this is a simple-type containing an enumeration. @return: True if enumeration, else False. @rtype: boolean """ return False def isattr(self): """ Get whether the object is a schema I{attribute} definition. @return: True if an attribute, else False. @rtype: boolean """ return False def extension(self): """ Get whether the object is an extension of another type. @return: True if an extension, else False. @rtype: boolean """ return False def restriction(self): """ Get whether the object is an restriction of another type. @return: True if a restriction, else False. @rtype: boolean """ return False def mixed(self): """Get whether the object has I{mixed} content.""" return False def find(self, qref, classes=[], ignore=None): """ Find a referenced type in self or children. Return None if not found. Qualified references for all schema objects checked in this search will be added to the set of ignored qualified references to avoid the find operation going into an infinite loop in case of recursively defined structures. @param qref: A qualified reference. @type qref: qref @param classes: A collection of classes used to qualify the match. @type classes: Collection(I{class},...), e.g. [I(class),...] @param ignore: A set of qualified references to ignore in this search. @type ignore: {qref,...} @return: The referenced type. @rtype: L{SchemaObject} @see: L{qualify()} """ if not len(classes): classes = (self.__class__,) if ignore is None: ignore = set() if self.qname in ignore: return ignore.add(self.qname) if self.qname == qref and self.__class__ in classes: return self for c in self.rawchildren: p = c.find(qref, classes, ignore=ignore) if p is not None: return p def translate(self, value, topython=True): """ Translate between an XSD type values and Python objects. When converting a Python object to an XSD type value the operation may return any Python object whose string representation matches the desired XSD type value. @param value: A value to translate. @type value: str if topython is True; any Python object otherwise @param topython: Flag indicating the translation direction. @type topython: bool @return: The converted I{language} type. """ return value def childtags(self): """ Get a list of valid child tag names. @return: A list of child tag names. @rtype: [str,...] """ return () def dependencies(self): """ Get a list of dependencies for dereferencing. @return: A merge dependency index and a list of dependencies. @rtype: (int, [L{SchemaObject},...]) """ return None, [] def autoqualified(self): """ The list of I{auto} qualified attribute values. Qualification means to convert values into I{qref}. @return: A list of attribute names. @rtype: list """ return ["type", "ref"] def qualify(self): """ Convert reference attribute values into a I{qref}. Constructed I{qref} uses the default document namespace. Since many WSDL schemas are written improperly: when the document does not define its default namespace, the schema target namespace is used to qualify references. """ defns = self.root.defaultNamespace() if Namespace.none(defns): defns = self.schema.tns for a in self.autoqualified(): ref = getattr(self, a) if ref is None: continue if isqref(ref): continue qref = qualify(ref, self.root, defns) log.debug("%s, convert %s='%s' to %s", self.id, a, ref, qref) setattr(self, a, qref) def merge(self, other): """Merge another object as needed.""" other.qualify() for n in ("default", "max", "min", "name", "nillable", "qname", "type"): if getattr(self, n) is not None: continue v = getattr(other, n) if v is None: continue setattr(self, n, v) def content(self, collection=None, filter=Filter(), history=None): """ Get a I{flattened} list of this node's contents. @param collection: A list to fill. @type collection: list @param filter: A filter used to constrain the result. @type filter: L{Filter} @param history: The history list used to prevent cyclic dependency. @type history: list @return: The filled list. @rtype: list """ if collection is None: collection = [] if history is None: history = [] if self in history: return collection history.append(self) if self in filter: collection.append(self) for c in self.rawchildren: c.content(collection, filter, history) history.pop() return collection def str(self, indent=0, history=None): """ Get a string representation of this object. @param indent: The indent. @type indent: int @return: A string. @rtype: str """ if history is None: history = [] if self in history: return "%s ..." % Repr(self) history.append(self) tab = "%*s" % (indent * 3, "") result = ["%s<%s" % (tab, self.id)] for n in self.description(): if not hasattr(self, n): continue v = getattr(self, n) if v is None: continue result.append(' %s="%s"' % (n, v)) if len(self): result.append(">") for c in self.rawchildren: result.append("\n") result.append(c.str(indent+1, history[:])) if c.isattr(): result.append("@") result.append("\n%s" % (tab,)) result.append("" % (self.__class__.__name__,)) else: result.append(" />") return "".join(result) def description(self): """ Get the names used for repr() and str() description. @return: A dictionary of relevant attributes. @rtype: [str,...] """ return () def __unicode__(self): return str(self.str()) def __repr__(self): s = [] s.append("<%s" % (self.id,)) for n in self.description(): if not hasattr(self, n): continue v = getattr(self, n) if v is None: continue s.append(' %s="%s"' % (n, v)) s.append(" />") return "".join(s) def __len__(self): n = 0 for x in self: n += 1 return n def __iter__(self): return Iter(self) def __getitem__(self, index): """ Returns a contained schema object referenced by its 0-based index. Returns None if such an object does not exist. """ i = 0 for c in self: if i == index: return c i += 1 class Iter: """ The content iterator - used to iterate the L{Content} children. The iterator provides a I{view} of the children that is free of container elements such as , or . @ivar stack: A stack used to control nesting. @type stack: list """ class Frame: """A content iterator frame.""" def __init__(self, sx): """ @param sx: A schema object. @type sx: L{SchemaObject} """ self.sx = sx self.items = sx.rawchildren self.index = 0 def __next__(self): """ Get the I{next} item in the frame's collection. @return: The next item or None @rtype: L{SchemaObject} """ if self.index < len(self.items): result = self.items[self.index] self.index += 1 return result def __iter__(self): return self def __init__(self, sx): """ @param sx: A schema object. @type sx: L{SchemaObject} """ self.stack = [] self.push(sx) def push(self, sx): """ Create a frame and push the specified object. @param sx: A schema object to push. @type sx: L{SchemaObject} """ self.stack.append(Iter.Frame(sx)) def pop(self): """ Pop the I{top} frame. @return: The popped frame. @rtype: L{Frame} @raise StopIteration: when stack is empty. """ if self.stack: return self.stack.pop() raise StopIteration() def top(self): """ Get the I{top} frame. @return: The top frame. @rtype: L{Frame} @raise StopIteration: when stack is empty. """ if self.stack: return self.stack[-1] raise StopIteration() def __next__(self): """ Get the next item. @return: A tuple: the next (child, ancestry). @rtype: (L{SchemaObject}, [L{SchemaObject},..]) @raise StopIteration: A the end. """ frame = self.top() while True: result = next(frame) if result is None: self.pop() return next(self) if isinstance(result, Content): ancestry = [f.sx for f in self.stack] return result, ancestry self.push(result) return next(self) def __iter__(self): return self class XBuiltin(SchemaObject): """Represents a built-in XSD schema node.""" def __init__(self, schema, name): """ @param schema: The containing schema. @type schema: L{schema.Schema} """ root = Element(name) SchemaObject.__init__(self, schema, root) self.name = name self.nillable = True def namespace(self, prefix=None): return Namespace.xsdns def builtin(self): return True class Content(SchemaObject): """XSD schema objects representing real XML document content.""" pass class NodeFinder: """ Find nodes based on flexable criteria. I{matcher} may be any object implementing a match(n) method. @ivar matcher: An object used as criteria for match. @type matcher: I{any}.match(n) @ivar limit: Limit the number of matches. 0=unlimited. @type limit: int """ def __init__(self, matcher, limit=0): """ @param matcher: An object used as criteria for match. @type matcher: I{any}.match(n) @param limit: Limit the number of matches. 0=unlimited. @type limit: int """ self.matcher = matcher self.limit = limit def find(self, node, list): """ Traverse the tree looking for matches. @param node: A node to match on. @type node: L{SchemaObject} @param list: A list to fill. @type list: list """ if self.matcher.match(node): list.append(node) self.limit -= 1 if self.limit == 0: return for c in node.rawchildren: self.find(c, list) return self suds-1.1.2/suds/xsd/sxbasic.py000066400000000000000000000574761425611400200162710ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """Classes representing I{basic} XSD schema objects.""" from suds import * from suds.reader import DocumentReader from suds.sax import Namespace from suds.transport import TransportError from suds.xsd import * from suds.xsd.query import * from suds.xsd.sxbase import * from urllib.parse import urljoin from logging import getLogger log = getLogger(__name__) class RestrictionMatcher: """For use with L{NodeFinder} to match restriction.""" def match(self, n): return isinstance(n, Restriction) class TypedContent(Content): """Represents any I{typed} content.""" def __init__(self, *args, **kwargs): Content.__init__(self, *args, **kwargs) self.resolved_cache = {} def resolve(self, nobuiltin=False): """ Resolve the node's type reference and return the referenced type node. Returns self if the type is defined locally, e.g. as a subnode. Otherwise returns the referenced external node. @param nobuiltin: Flag indicating whether resolving to XSD built-in types should not be allowed. @return: The resolved (true) type. @rtype: L{SchemaObject} """ cached = self.resolved_cache.get(nobuiltin) if cached is not None: return cached resolved = self.__resolve_type(nobuiltin) self.resolved_cache[nobuiltin] = resolved return resolved def __resolve_type(self, nobuiltin=False): """ Private resolve() worker without any result caching. @param nobuiltin: Flag indicating whether resolving to XSD built-in types should not be allowed. @return: The resolved (true) type. @rtype: L{SchemaObject} """ # There is no need for a recursive implementation here since a node can # reference an external type node but XSD specification explicitly # states that that external node must not be a reference to yet another # node. qref = self.qref() if qref is None: return self query = TypeQuery(qref) query.history = [self] log.debug("%s, resolving: %s\n using:%s", self.id, qref, query) resolved = query.execute(self.schema) if resolved is None: log.debug(self.schema) raise TypeNotFound(qref) if resolved.builtin() and nobuiltin: return self return resolved def qref(self): """ Get the I{type} qualified reference to the referenced XSD type. This method takes into account simple types defined through restriction which are detected by determining that self is simple (len == 0) and by finding a restriction child. @return: The I{type} qualified reference. @rtype: qref """ qref = self.type if qref is None and len(self) == 0: ls = [] m = RestrictionMatcher() finder = NodeFinder(m, 1) finder.find(self, ls) if ls: return ls[0].ref return qref class Complex(SchemaObject): """ Represents an XSD schema node. @cvar childtags: A list of valid child node names. @type childtags: (I{str},...) """ def childtags(self): return ("all", "any", "attribute", "attributeGroup", "choice", "complexContent", "group", "sequence", "simpleContent") def description(self): return ("name",) def extension(self): for c in self.rawchildren: if c.extension(): return True return False def mixed(self): for c in self.rawchildren: if isinstance(c, SimpleContent) and c.mixed(): return True return False class Group(SchemaObject): """ Represents an XSD schema node. @cvar childtags: A list of valid child node names. @type childtags: (I{str},...) """ def childtags(self): return "all", "choice", "sequence" def dependencies(self): deps = [] midx = None if self.ref is not None: query = GroupQuery(self.ref) g = query.execute(self.schema) if g is None: log.debug(self.schema) raise TypeNotFound(self.ref) deps.append(g) midx = 0 return midx, deps def merge(self, other): SchemaObject.merge(self, other) self.rawchildren = other.rawchildren def description(self): return "name", "ref" class AttributeGroup(SchemaObject): """ Represents an XSD schema node. @cvar childtags: A list of valid child node names. @type childtags: (I{str},...) """ def childtags(self): return "attribute", "attributeGroup" def dependencies(self): deps = [] midx = None if self.ref is not None: query = AttrGroupQuery(self.ref) ag = query.execute(self.schema) if ag is None: log.debug(self.schema) raise TypeNotFound(self.ref) deps.append(ag) midx = 0 return midx, deps def merge(self, other): SchemaObject.merge(self, other) self.rawchildren = other.rawchildren def description(self): return "name", "ref" class Simple(SchemaObject): """Represents an XSD schema node.""" def childtags(self): return "any", "list", "restriction" def enum(self): for child, ancestry in self.children(): if isinstance(child, Enumeration): return True return False def mixed(self): return len(self) def description(self): return ("name",) def extension(self): for c in self.rawchildren: if c.extension(): return True return False def restriction(self): for c in self.rawchildren: if c.restriction(): return True return False class List(SchemaObject): """Represents an XSD schema node.""" def childtags(self): return () def description(self): return ("name",) def xslist(self): return True class Restriction(SchemaObject): """Represents an XSD schema node.""" def __init__(self, schema, root): SchemaObject.__init__(self, schema, root) self.ref = root.get("base") def childtags(self): return "attribute", "attributeGroup", "enumeration" def dependencies(self): deps = [] midx = None if self.ref is not None: query = TypeQuery(self.ref) super = query.execute(self.schema) if super is None: log.debug(self.schema) raise TypeNotFound(self.ref) if not super.builtin(): deps.append(super) midx = 0 return midx, deps def restriction(self): return True def merge(self, other): SchemaObject.merge(self, other) filter = Filter(False, self.rawchildren) self.prepend(self.rawchildren, other.rawchildren, filter) def description(self): return ("ref",) class Collection(SchemaObject): """Represents an XSD schema collection (a.k.a. order indicator) node.""" def childtags(self): return "all", "any", "choice", "element", "group", "sequence" class All(Collection): """Represents an XSD schema node.""" def all(self): return True class Choice(Collection): """Represents an XSD schema node.""" def choice(self): return True class Sequence(Collection): """Represents an XSD schema node.""" def sequence(self): return True class ComplexContent(SchemaObject): """Represents an XSD schema node.""" def childtags(self): return "attribute", "attributeGroup", "extension", "restriction" def extension(self): for c in self.rawchildren: if c.extension(): return True return False def restriction(self): for c in self.rawchildren: if c.restriction(): return True return False class SimpleContent(SchemaObject): """Represents an XSD schema node.""" def childtags(self): return "extension", "restriction" def extension(self): for c in self.rawchildren: if c.extension(): return True return False def restriction(self): for c in self.rawchildren: if c.restriction(): return True return False def mixed(self): return len(self) class Enumeration(Content): """Represents an XSD schema node.""" def __init__(self, schema, root): Content.__init__(self, schema, root) self.name = root.get("value") def description(self): return ("name",) def enum(self): return True class Element(TypedContent): """Represents an XSD schema node.""" def __init__(self, schema, root): TypedContent.__init__(self, schema, root) is_reference = self.ref is not None is_top_level = root.parent is schema.root if is_reference or is_top_level: self.form_qualified = True else: form = root.get("form") if form is not None: self.form_qualified = (form == "qualified") nillable = self.root.get("nillable") if nillable is not None: self.nillable = (nillable in ("1", "true")) self.implany() def implany(self): """ Set the type to when implicit. An element has an implicit type when it has no body and no explicitly defined type. @return: self @rtype: L{Element} """ if self.type is None and self.ref is None and self.root.isempty(): self.type = self.anytype() def childtags(self): return "any", "attribute", "complexType", "simpleType" def extension(self): for c in self.rawchildren: if c.extension(): return True return False def restriction(self): for c in self.rawchildren: if c.restriction(): return True return False def dependencies(self): deps = [] midx = None e = self.__deref() if e is not None: deps.append(e) midx = 0 return midx, deps def merge(self, other): SchemaObject.merge(self, other) self.rawchildren = other.rawchildren def description(self): return "name", "ref", "type" def anytype(self): """Create an xsd:anyType reference.""" p, u = Namespace.xsdns mp = self.root.findPrefix(u) if mp is None: mp = p self.root.addPrefix(p, u) return ":".join((mp, "anyType")) def namespace(self, prefix=None): """ Get this schema element's target namespace. In case of reference elements, the target namespace is defined by the referenced and not the referencing element node. @param prefix: The default prefix. @type prefix: str @return: The schema element's target namespace @rtype: (I{prefix},I{URI}) """ e = self.__deref() if e is not None: return e.namespace(prefix) return super(Element, self).namespace() def __deref(self): if self.ref is None: return query = ElementQuery(self.ref) e = query.execute(self.schema) if e is None: log.debug(self.schema) raise TypeNotFound(self.ref) return e class Extension(SchemaObject): """Represents an XSD schema node.""" def __init__(self, schema, root): SchemaObject.__init__(self, schema, root) self.ref = root.get("base") def childtags(self): return ("all", "attribute", "attributeGroup", "choice", "group", "sequence") def dependencies(self): deps = [] midx = None if self.ref is not None: query = TypeQuery(self.ref) super = query.execute(self.schema) if super is None: log.debug(self.schema) raise TypeNotFound(self.ref) if not super.builtin(): deps.append(super) midx = 0 return midx, deps def merge(self, other): SchemaObject.merge(self, other) filter = Filter(False, self.rawchildren) self.prepend(self.rawchildren, other.rawchildren, filter) def extension(self): return self.ref is not None def description(self): return ("ref",) class Import(SchemaObject): """ Represents an XSD schema node. @cvar locations: A dictionary of namespace locations. @type locations: dict @ivar ns: The imported namespace. @type ns: str @ivar location: The (optional) location. @type location: namespace-uri @ivar opened: Opened and I{imported} flag. @type opened: boolean """ locations = {} @classmethod def bind(cls, ns, location=None): """ Bind a namespace to a schema location (URI). This is used for imports that do not specify a schemaLocation. @param ns: A namespace-uri. @type ns: str @param location: The (optional) schema location for the namespace. (default=ns) @type location: str """ if location is None: location = ns cls.locations[ns] = location def __init__(self, schema, root): SchemaObject.__init__(self, schema, root) self.ns = (None, root.get("namespace")) self.location = root.get("schemaLocation") if self.location is None: self.location = self.locations.get(self.ns[1]) self.opened = False def open(self, options, loaded_schemata): """ Open and import the referenced schema. @param options: An options dictionary. @type options: L{options.Options} @param loaded_schemata: Already loaded schemata cache (URL --> Schema). @type loaded_schemata: dict @return: The referenced schema. @rtype: L{Schema} """ if self.opened: return self.opened = True log.debug("%s, importing ns='%s', location='%s'", self.id, self.ns[1], self.location) result = self.__locate() if result is None: if self.location is None: log.debug("imported schema (%s) not-found", self.ns[1]) else: url = self.location if "://" not in url: url = urljoin(self.schema.baseurl, url) result = (loaded_schemata.get(url) or self.__download(url, loaded_schemata, options)) log.debug("imported:\n%s", result) return result def __locate(self): """Find the schema locally.""" if self.ns[1] != self.schema.tns[1]: return self.schema.locate(self.ns) def __download(self, url, loaded_schemata, options): """Download the schema.""" try: reader = DocumentReader(options) d = reader.open(url) root = d.root() root.set("url", url) return self.schema.instance(root, url, loaded_schemata, options) except TransportError: msg = "import schema (%s) at (%s), failed" % (self.ns[1], url) log.error("%s, %s", self.id, msg, exc_info=True) raise Exception(msg) def description(self): return "ns", "location" class Include(SchemaObject): """ Represents an XSD schema node. @ivar location: The (optional) location. @type location: namespace-uri @ivar opened: Opened and I{imported} flag. @type opened: boolean """ locations = {} def __init__(self, schema, root): SchemaObject.__init__(self, schema, root) self.location = root.get("schemaLocation") if self.location is None: self.location = self.locations.get(self.ns[1]) self.opened = False def open(self, options, loaded_schemata): """ Open and include the referenced schema. @param options: An options dictionary. @type options: L{options.Options} @param loaded_schemata: Already loaded schemata cache (URL --> Schema). @type loaded_schemata: dict @return: The referenced schema. @rtype: L{Schema} """ if self.opened: return self.opened = True log.debug("%s, including location='%s'", self.id, self.location) url = self.location if "://" not in url: url = urljoin(self.schema.baseurl, url) result = (loaded_schemata.get(url) or self.__download(url, loaded_schemata, options)) log.debug("included:\n%s", result) return result def __download(self, url, loaded_schemata, options): """Download the schema.""" try: reader = DocumentReader(options) d = reader.open(url) root = d.root() root.set("url", url) self.__applytns(root) return self.schema.instance(root, url, loaded_schemata, options) except TransportError: msg = "include schema at (%s), failed" % url log.error("%s, %s", self.id, msg, exc_info=True) raise Exception(msg) def __applytns(self, root): """Make sure included schema has the same target namespace.""" TNS = "targetNamespace" tns = root.get(TNS) if tns is None: tns = self.schema.tns[1] root.set(TNS, tns) else: if self.schema.tns[1] != tns: raise Exception("%s mismatch" % TNS) def description(self): return "location" class Attribute(TypedContent): """Represents an XSD schema node.""" def __init__(self, schema, root): TypedContent.__init__(self, schema, root) self.use = root.get("use", default="") def childtags(self): return ("restriction",) def isattr(self): return True def get_default(self): """ Gets the attribute value. @return: The default value for the attribute @rtype: str """ return self.root.get("default", default="") def optional(self): return self.use != "required" def dependencies(self): deps = [] midx = None if self.ref is not None: query = AttrQuery(self.ref) a = query.execute(self.schema) if a is None: log.debug(self.schema) raise TypeNotFound(self.ref) deps.append(a) midx = 0 return midx, deps def description(self): return "name", "ref", "type" class Any(Content): """Represents an XSD schema node.""" def get_child(self, name): root = self.root.clone() root.set("note", "synthesized (any) child") child = Any(self.schema, root) return child, [] def get_attribute(self, name): root = self.root.clone() root.set("note", "synthesized (any) attribute") attribute = Any(self.schema, root) return attribute, [] def any(self): return True class Factory: """ @cvar tags: A factory to create object objects based on tag. @type tags: {tag:fn,} """ tags = { "all": All, "any": Any, "attribute": Attribute, "attributeGroup": AttributeGroup, "choice": Choice, "complexContent": ComplexContent, "complexType": Complex, "element": Element, "enumeration": Enumeration, "extension": Extension, "group": Group, "import": Import, "include": Include, "list": List, "restriction": Restriction, "simpleContent": SimpleContent, "simpleType": Simple, "sequence": Sequence, } @classmethod def maptag(cls, tag, fn): """ Map (override) tag => I{class} mapping. @param tag: An XSD tag name. @type tag: str @param fn: A function or class. @type fn: fn|class. """ cls.tags[tag] = fn @classmethod def create(cls, root, schema): """ Create an object based on the root tag name. @param root: An XML root element. @type root: L{Element} @param schema: A schema object. @type schema: L{schema.Schema} @return: The created object. @rtype: L{SchemaObject} """ fn = cls.tags.get(root.name) if fn is not None: return fn(schema, root) @classmethod def build(cls, root, schema, filter=("*",)): """ Build an xsobject representation. @param root: An schema XML root. @type root: L{sax.element.Element} @param filter: A tag filter. @type filter: [str,...] @return: A schema object graph. @rtype: L{sxbase.SchemaObject} """ children = [] for node in root.getChildren(ns=Namespace.xsdns): if "*" in filter or node.name in filter: child = cls.create(node, schema) if child is None: continue children.append(child) c = cls.build(node, schema, child.childtags()) child.rawchildren = c return children @classmethod def collate(cls, children): imports = [] elements = {} attributes = {} types = {} groups = {} agrps = {} for c in children: if isinstance(c, (Import, Include)): imports.append(c) continue if isinstance(c, Attribute): attributes[c.qname] = c continue if isinstance(c, Element): elements[c.qname] = c continue if isinstance(c, Group): groups[c.qname] = c continue if isinstance(c, AttributeGroup): agrps[c.qname] = c continue types[c.qname] = c for i in imports: children.remove(i) return children, imports, attributes, elements, types, groups, agrps ####################################################### # Static Import Bindings :-( ####################################################### Import.bind( "http://schemas.xmlsoap.org/soap/encoding/", "suds://schemas.xmlsoap.org/soap/encoding/") Import.bind( "http://www.w3.org/XML/1998/namespace", "http://www.w3.org/2001/xml.xsd") Import.bind( "http://www.w3.org/2001/XMLSchema", "http://www.w3.org/2001/XMLSchema.xsd") suds-1.1.2/suds/xsd/sxbuiltin.py000066400000000000000000000253011425611400200166340ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """Classes representing I{built-in} XSD schema objects.""" from suds import * from suds.xsd import * from suds.sax.date import * from suds.xsd.sxbase import XBuiltin import datetime import decimal import sys class XAny(XBuiltin): """Represents an XSD node.""" def __init__(self, schema, name): XBuiltin.__init__(self, schema, name) self.nillable = False def get_child(self, name): child = XAny(self.schema, name) return child, [] def any(self): return True class XBoolean(XBuiltin): """Represents an XSD boolean built-in type.""" _xml_to_python = {"1": True, "true": True, "0": False, "false": False} _python_to_xml = {True: "true", 1: "true", False: "false", 0: "false"} @staticmethod def translate(value, topython=True): if topython: if isinstance(value, str): return XBoolean._xml_to_python.get(value) else: if isinstance(value, (bool, int)): return XBoolean._python_to_xml.get(value) return value class XDate(XBuiltin): """Represents an XSD built-in type.""" @staticmethod def translate(value, topython=True): if topython: if isinstance(value, str) and value: return Date(value).value else: if isinstance(value, datetime.date): return Date(value) return value class XDateTime(XBuiltin): """Represents an XSD built-in type.""" @staticmethod def translate(value, topython=True): if topython: if isinstance(value, str) and value: return DateTime(value).value else: if isinstance(value, datetime.datetime): return DateTime(value) return value class XDecimal(XBuiltin): """ Represents an XSD built-in type. Excerpt from the XSD datatype specification (http://www.w3.org/TR/2004/REC-xmlschema-2-20041028): > 3.2.3 decimal > > [Definition:] decimal represents a subset of the real numbers, which can > be represented by decimal numerals. The ·value space· of decimal is the > set of numbers that can be obtained by multiplying an integer by a > non-positive power of ten, i.e., expressible as i × 10^-n where i and n > are integers and n >= 0. Precision is not reflected in this value space; > the number 2.0 is not distinct from the number 2.00. The ·order-relation· > on decimal is the order relation on real numbers, restricted to this > subset. > > 3.2.3.1 Lexical representation > > decimal has a lexical representation consisting of a finite-length > sequence of decimal digits (#x30-#x39) separated by a period as a decimal > indicator. An optional leading sign is allowed. If the sign is omitted, > "+" is assumed. Leading and trailing zeroes are optional. If the > fractional part is zero, the period and following zero(es) can be > omitted. For example: -1.23, 12678967.543233, +100000.00, 210. """ # Python versions before 2.7 do not support the decimal.Decimal.canonical() # method but they maintain their decimal.Decimal encoded in canonical # format internally so we can easily emulate that function by simply # returning the same decimal instance. if sys.version_info < (2, 7): _decimal_canonical = staticmethod(lambda decimal: decimal) else: _decimal_canonical = decimal.Decimal.canonical @staticmethod def _decimal_to_xsd_format(value): """ Converts a decimal.Decimal value to its XSD decimal type value. Result is a string containing the XSD decimal type's lexical value representation. The conversion is done without any precision loss. Note that Python's native decimal.Decimal string representation will not do here as the lexical representation desired here does not allow representing decimal values using float-like `E' format, e.g. 12E+30 or 0.10006E-12. """ value = XDecimal._decimal_canonical(value) negative, digits, exponent = value.as_tuple() # The following implementation assumes the following tuple decimal # encoding (part of the canonical decimal value encoding): # - digits must contain at least one element # - no leading integral 0 digits except a single one in 0 (if a non-0 # decimal value has leading integral 0 digits they must be encoded # in its 'exponent' value and not included explicitly in its # 'digits' tuple) assert digits assert digits[0] != 0 or len(digits) == 1 result = [] if negative: result.append("-") # No fractional digits. if exponent >= 0: result.extend(str(x) for x in digits) result.extend("0" * exponent) return "".join(result) digit_count = len(digits) # Decimal point offset from the given digit start. point_offset = digit_count + exponent # Trim trailing fractional 0 digits. fractional_digit_count = min(digit_count, -exponent) while fractional_digit_count and digits[digit_count - 1] == 0: digit_count -= 1 fractional_digit_count -= 1 # No trailing fractional 0 digits and a decimal point coming not after # the given digits, meaning there is no need to add additional trailing # integral 0 digits. if point_offset <= 0: # No integral digits. result.append("0") if digit_count > 0: result.append(".") result.append("0" * -point_offset) result.extend(str(x) for x in digits[:digit_count]) else: # Have integral and possibly some fractional digits. result.extend(str(x) for x in digits[:point_offset]) if point_offset < digit_count: result.append(".") result.extend(str(x) for x in digits[point_offset:digit_count]) return "".join(result) @classmethod def translate(cls, value, topython=True): if topython: if isinstance(value, str) and value: return decimal.Decimal(value) else: if isinstance(value, decimal.Decimal): return cls._decimal_to_xsd_format(value) return value class XFloat(XBuiltin): """Represents an XSD built-in type.""" @staticmethod def translate(value, topython=True): if topython: if isinstance(value, str) and value: return float(value) else: return value class XInteger(XBuiltin): """Represents an XSD built-in type.""" @staticmethod def translate(value, topython=True): if topython: if isinstance(value, str) and value: return int(value) else: return value class XLong(XBuiltin): """Represents an XSD built-in type.""" @staticmethod def translate(value, topython=True): if topython: if isinstance(value, str) and value: return int(value) else: return value class XString(XBuiltin): """Represents an XSD node.""" pass class XTime(XBuiltin): """Represents an XSD built-in type.""" @staticmethod def translate(value, topython=True): if topython: if isinstance(value, str) and value: return Time(value).value else: if isinstance(value, datetime.time): return Time(value) return value class Factory: tags = { # any "anyType": XAny, # strings "string": XString, "normalizedString": XString, "ID": XString, "Name": XString, "QName": XString, "NCName": XString, "anySimpleType": XString, "anyURI": XString, "NOTATION": XString, "token": XString, "language": XString, "IDREFS": XString, "ENTITIES": XString, "IDREF": XString, "ENTITY": XString, "NMTOKEN": XString, "NMTOKENS": XString, # binary "hexBinary": XString, "base64Binary": XString, # integers "int": XInteger, "integer": XInteger, "unsignedInt": XInteger, "positiveInteger": XInteger, "negativeInteger": XInteger, "nonPositiveInteger": XInteger, "nonNegativeInteger": XInteger, # longs "long": XLong, "unsignedLong": XLong, # shorts "short": XInteger, "unsignedShort": XInteger, "byte": XInteger, "unsignedByte": XInteger, # floats "float": XFloat, "double": XFloat, "decimal": XDecimal, # dates & times "date": XDate, "time": XTime, "dateTime": XDateTime, "duration": XString, "gYearMonth": XString, "gYear": XString, "gMonthDay": XString, "gDay": XString, "gMonth": XString, # boolean "boolean": XBoolean, } @classmethod def maptag(cls, tag, fn): """ Map (override) tag => I{class} mapping. @param tag: An XSD tag name. @type tag: str @param fn: A function or class. @type fn: fn|class. """ cls.tags[tag] = fn @classmethod def create(cls, schema, name): """ Create an object based on the root tag name. @param schema: A schema object. @type schema: L{schema.Schema} @param name: The name. @type name: str @return: The created object. @rtype: L{XBuiltin} """ fn = cls.tags.get(name, XBuiltin) return fn(schema, name) suds-1.1.2/tests/000077500000000000000000000000001425611400200136265ustar00rootroot00000000000000suds-1.1.2/tests/conftest.py000066400000000000000000000030751425611400200160320ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ pytest configuration file for the suds test suite. """ # Make pytest load custom plugins expected to be loaded in our test suite. #TODO: pytest (tested up to version 2.5.1) will not display our plugin marker # information in its --markers list if called from a folder other than the one # containing the tests folder or if the tests folder is not on the current # Python path, e.g. if using pytest in the Python 3 implementation 'build' # folder constructed by 'setup.py build' using 'py.test build --markers'. The # plugin will still get loaded correctly when actually running the tests. This # has already been reported as a pytest issue. pytest_plugins = "testutils.indirect_parametrize" suds-1.1.2/tests/external/000077500000000000000000000000001425611400200154505ustar00rootroot00000000000000suds-1.1.2/tests/external/__init__.py000066400000000000000000000017451425611400200175700ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Test scripts inherited from the original suds library implementation, requiring external resources or simply untested. """ suds-1.1.2/tests/external/axis1.py000066400000000000000000000236351425611400200170600ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) # # This test requires installation or visibility of my local axis(1) server. # import sys sys.path.append('../../') from suds import WebFault from suds.client import Client from suds.sudsobject import Object from suds.transport.https import HttpAuthenticated from suds.plugin import * import traceback as tb errors = 0 credentials = dict(username='jortel', password='abc123') class MyInitPlugin(InitPlugin): def initialized(self, context): print('PLUGIN (init): initialized: ctx=%s' % (context.__dict__,)) class MyDocumentPlugin(DocumentPlugin): def loaded(self, context): print('PLUGIN (document): loaded: ctx=%s' % (context.__dict__,)) def parsed(self, context): print('PLUGIN (document): parsed: ctx=%s' % (context.__dict__,)) class MyMessagePlugin(MessagePlugin): def marshalled(self, context): print('PLUGIN (message): marshalled: ctx=%s' % (context.__dict__,)) def sending(self, context): print('PLUGIN (message): sending: ctx=%s' % (context.__dict__,)) def received(self, context): print('PLUGIN (message): received: ctx=%s' % (context.__dict__,)) def parsed(self, context): print('PLUGIN (message): parsed: ctx=%s' % (context.__dict__,)) def unmarshalled(self, context): print('PLUGIN: (massage): unmarshalled: ctx=%s' % (context.__dict__,)) myplugins = ( MyInitPlugin(), MyDocumentPlugin(), MyMessagePlugin(), ) def start(url): global errors print('\n______________________________________________________________\n') print('Test @ ( %s )\nerrors = %d\n' % (url, errors)) try: url = 'http://localhost:8081/axis/services/basic-rpc-encoded?wsdl' start(url) t = HttpAuthenticated(**credentials) client = Client(url, transport=t, cache=None, plugins=myplugins) print(client) # # create a name object using the wsdl # print('create name') name = client.factory.create('ns0:Name') name.first = u'jeff'+unichr(1234) name.last = 'ortel' print(name) # # create a phone object using the wsdl # print('create phone') phoneA = client.factory.create('ns0:Phone') phoneA.npa = 410 phoneA.nxx = 555 phoneA.number = 5138 phoneB = client.factory.create('ns0:Phone') phoneB.npa = 919 phoneB.nxx = 555 phoneB.number = 4406 phoneC = { 'npa':205, 'nxx':777, 'number':1212 } # # create a dog # dog = client.factory.create('ns0:Dog') dog.name = 'Chance' dog.trained = True # # create a person object using the wsdl # person = client.factory.create('ns0:Person') print('{empty} person=\n%s' % (person,)) person.name = name person.age = 43 person.phone = [phoneA,phoneB,phoneC] person.pets = [dog] print('person=\n%s' % (person,)) # # add the person (using the webservice) # print('addPersion()') result = client.service.addPerson(person) print('\nreply(\n%s\n)\n' % (str(result),)) # # Async # client.options.nosend=True reply = 'person (jeffӒ,ortel) at age 43 with phone numbers (410-555-5138,919-555-4406,205-777-1212, and pets (Chance,) - added.' request = client.service.addPerson(person) result = request.succeeded(reply) error = Object() error.httpcode = '500' client.options.nosend=False # request.failed(error) # # # create a new name object used to update the person # newname = client.factory.create('ns0:Name') newname.first = 'Todd' newname.last = None # # create AnotherPerson using Person # ap = client.factory.create('ns0:AnotherPerson') ap.name = person.name ap.age = person.age ap.phone = person.phone ap.pets = person.pets print('AnotherPerson\n%s' % (ap,)) # # update the person's name (using the webservice) # print('updatePersion()') result = client.service.updatePerson(ap, newname) print('\nreply(\n%s\n)\n' % (str(result),)) result = client.service.updatePerson(ap, None) print('\nreply(\n%s\n)\n' % (str(result),)) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: url = 'http://localhost:8081/axis/services/basic-rpc-encoded?wsdl' start(url) t = HttpAuthenticated(**credentials) client = Client(url, transport=t, cache=None) print(client) # # create a name object as dict # print('create name') name = {} name['first'] = 'Elmer' name['last'] = 'Fudd' print(name) # # create a phone as dict # print('create phone') phoneA = {} phoneA['npa'] = 410 phoneA['nxx'] = 555 phoneA['number'] = 5138 phoneB = {} phoneB['npa'] = 919 phoneB['nxx'] = 555 phoneB['number'] = 4406 phoneC = { 'npa':205, 'nxx':777, 'number':1212 } # # create a dog # dog = { 'name':'Chance', 'trained':True, } # # create a person as dict # person = {} print('{empty} person=\n%s' % (person,)) person['name'] = name person['age'] = 43 person['phone'] = [phoneA, phoneB, phoneC] person['pets'] = [dog] print('person=\n%s' % (person,)) # # add the person (using the webservice) # print('addPersion()') result = client.service.addPerson(person) print('\nreply(\n%s\n)\n' % (str(result),)) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: print("echo(' this is cool ')") result = client.service.echo('this is cool') print('\nreply( "%s" )\n' % (str(result),)) print('echo(None)') result = client.service.echo(None) print('\nreply( "%s" )\n' % (str(result),)) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: print('hello()') result = client.service.hello() print('\nreply( %s )\n' % (str(result),)) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: print('testVoid()') result = client.service.getVoid() print('\nreply( %s )\n' % (str(result),)) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: print('** new style arrays **') words = ['my', 'dog', 'likes', 'steak'] result = client.service.printList(words) print('\nreply( %s )\n' % (str(result),)) print('** old style arrays **') array = client.factory.create('ArrayOf_xsd_string') print('ArrayOf_xsd_string=\n%s' % (array,)) array.item = ['my', 'dog', 'likes', 'steak'] result = client.service.printList(array) print('\nreply( %s )\n' % (str(result),)) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: s = 'hello' for n in range(0, 3): print('getList(%s, %d)' % (s, n)) result = client.service.getList(s, n) print('\nreply( %s )\n' % (str(result),)) assert ( isinstance(result, list) and len(result) == n ) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: print('testExceptions()') result = client.service.throwException() print('\nreply( %s )\n' % (tostr(result),)) raise Exception('Fault expected and not raised') except WebFault as f: print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: url = 'http://localhost:8081/axis/services/basic-rpc-encoded?wsdl' start(url) client = Client(url, faults=False, **credentials) print('testExceptions()') result = client.service.throwException() print('\nreply( %s )\n' % (str(result),)) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() print('\nFinished: errors=%d' % (errors,)) suds-1.1.2/tests/external/axis2.py000066400000000000000000000115361425611400200170560ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) import sys sys.path.append('../../') from suds import * from suds.client import Client from datetime import datetime errors = 0 url = 'http://localhost:8080/axis2/services/BasicService?wsdl' print('url=%s' % (url,)) # # create a service client using the wsdl. # client = Client(url) # # print the service (introspection) # print(client) print('printList()') print(client.service.printList(['a', 'b'])) # # create a name object using the wsdl # print('create name') name = client.factory.create('ns2:Name') name.first = u'jeff'+unichr(1234) name.last = 'ortel' print(name) # # create a phone object using the wsdl # print('create phone') phoneA = client.factory.create('ns2:Phone') phoneA.npa = 410 phoneA.nxx = 822 phoneA.number = 5138 phoneB = client.factory.create('ns2:Phone') phoneB.npa = 919 phoneB.nxx = 606 phoneB.number = 4406 # # create a dog # dog = client.factory.create('ns2:Dog') print(dog) dog.name = 'Chance' dog.trained = True print(dog) # # create a person object using the wsdl # person = client.factory.create('ns2:Person') # # inspect empty person # print('{empty} person=\n%s' % (person,)) person.name = name person.age = None person.birthday = datetime.now() person.phone.append(phoneA) person.phone.append(phoneB) person.pets.append(dog) # # inspect person # print('person=\n%s' % (person,)) # # add the person (using the webservice) # print('addPersion()') result = client.service.addPerson(person) print('\nreply(\n%s\n)\n' % (result.encode('utf-8'),)) # # create a new name object used to update the person # newname = client.factory.create('ns2:Name') newname.first = 'Todd' newname.last = None # # update the person's name (using the webservice) and print return person object # print('updatePersion()') result = client.service.updatePerson(person, newname) print('\nreply(\n%s\n)\n' % (str(result),)) result = client.service.updatePerson(person, None) print('\nreply(\n%s\n)\n' % (str(result),)) # # invoke the echo service # print('echo()') client.service.echo(None) result = client.service.echo('this is cool') print('\nreply( %s )\n' % (str(result),)) print('echo() with {none}') result = client.service.echo(None) print('\nreply( %s )\n' % (str(result),)) # # invoke the hello service # print('hello()') result = client.service.hello() print('\nreply( %s )\n' % (str(result),)) # # invoke the testVoid service # try: print('getVoid()') result = client.service.getVoid() print('\nreply( %s )\n' % (str(result),)) except (KeyboardInterrupt, SystemExit): raise except Exception: print(sys.exc_info()[1]) # # test list args # print('getList(list)') mylist = ['my', 'dog', 'likes', 'steak'] result = client.service.printList(mylist) print('\nreply( %s )\n' % (str(result),)) # tuple print('testListArgs(tuple)') mylist = ('my', 'dog', 'likes', 'steak') result = client.service.printList(mylist) print('\nreply( %s )\n' % (str(result),)) # # test list returned # for n in range(0, 3): print('getList(str, %d)' % (n,)) result = client.service.getList('hello', n) print('\nreply( %s )\n' % (str(result),)) assert ( isinstance(result, list) and len(result) == n ) print('addPet()') dog = client.factory.create('ns2:Dog') dog.name = 'Chance' dog.trained = True print(dog) try: result = client.service.addPet(person, dog) print('\nreply( %s )\n' % (str(result),)) except (KeyboardInterrupt, SystemExit): raise except Exception: print(sys.exc_info()[1]) print('___________________ E X C E P T I O N S __________________________') # # test exceptions # try: print('throwException() faults=True') result = client.service.throwException() print('\nreply( %s )\n' % (tostr(result),)) except (KeyboardInterrupt, SystemExit): raise except Exception: print(sys.exc_info()[1]) # # test faults # try: print('throwException() faults=False') client.set_options(faults=False) result = client.service.throwException() print('\nreply( %s )\n' % (tostr(result),)) except (KeyboardInterrupt, SystemExit): raise except Exception: print(sys.exc_info()[1]) print('\nfinished: errors=%d' % (errors,)) suds-1.1.2/tests/external/jasper.py000066400000000000000000000030211425611400200173020ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) import sys sys.path.append('../../') from suds import WebFault from suds.client import Client import traceback as tb errors = 0 def start(url): print('\n______________________________________________________________\n') print('Test @ ( %s )' % (url,)) try: url = 'http://localhost:9090/jasperserver-pro/services/repository?wsdl' start(url) client = Client(url, username='jeff', password='ortel') print(client) print(client.service.list('')) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() print('\nFinished: errors = %d' % (errors,)) suds-1.1.2/tests/external/public.py000066400000000000000000000173551425611400200173130ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) import sys sys.path.append('../../') import traceback as tb import suds.metrics as metrics from suds import WebFault from suds.client import Client errors = 0 def start(url): global errors print('\n______________________________________________________________\n') print('Test @ ( %s ) %d' % (url, errors)) try: url = 'http://mssoapinterop.org/asmx/simple.asmx?WSDL' start(url) client = Client(url) print(client) # string input = "42" d = dict(inputString=input) result = client.service.echoString(**d) print('echoString() = %s' % (result,)) assert result == input # int input = 42 result = client.service.echoInteger(input) print('echoInteger() = %s' % (result,)) assert result == input # float input = 4.2 result = client.service.echoFloat(input) print('echoFloat() = %s' % (result,)) assert result == input # suds 0.3.8+ result = client.service.echoIntegerArray([]) print('echoIntegerArray() = %s' % (result,)) assert result is None input = [1, 2, 3, 4] result = client.service.echoIntegerArray(input) print('echoIntegerArray() = %s' % (result,)) assert result == input result = client.service.echoIntegerArray(inputIntegerArray=input) print('echoIntegerArray() = %s' % (result,)) assert result == input except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: url = 'http://jira.atlassian.com/rpc/soap/jirasoapservice-v2?wsdl' start(url) client = Client(url) print(client) token = client.service.login('soaptester', 'soaptester') print('token="%s"' % (token,)) user = client.service.getUser(token, 'soaptester') print('user="%s"' % (user,)) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: url = 'http://jira.atlassian.com/rpc/soap/jirasoapservice-v2?wsdl' start(url+' ** cloned **') client = Client(url).clone() print('**clone**\n%s' % (client,)) token = client.service.login('soaptester', 'soaptester') print('**clone** token="%s"' % (token,)) user = client.service.getUser(token, 'soaptester') print('**clone** user="%s"' % (user,)) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: url = ' http://www.boyzoid.com/comp/randomQuote.cfc?wsdl ' start(url) client = Client(url) print(client) print(client.service.getQuote(False)) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: url = 'http://www.zenfolio.com/zf/api/zfapi.asmx?wsdl' start(url) client = Client(url) print(client) #client.setport(0) group = client.factory.create('Group') print('Group:\n%s' % (group,)) print('LoadGroupHierarchy("demo")') groupHierarchy = client.service.LoadGroupHierarchy("demo") print('result:\n%s' % (groupHierarchy,)) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: url = 'http://cert.synxis.com/interface/ChannelConnect.asmx?WSDL' start(url) client = Client(url) print(client) #client.setport(0) tpa = client.factory.create('ns1:TPA_Extensions') print(client.service.Ping(tpa, "hello")) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: url = 'https://sec.neurofuzz-software.com/paos/genSSHA-SOAP.php?wsdl' start(url) client = Client(url) print(client) print(client.service.genSSHA('hello', 'sha1')) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: url = 'http://ap1314-dsr.compmed.ucdavis.edu/dataserver/Aperio.Images/Image?method=wsdl' start(url) client = Client(url) #print(client.factory.resolver.schema) print(client) print('Logon()') reply = client.service.Logon('testuser', 'test') print(reply) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: url = 'http://soa.ebrev.info/service.wsdl' start(url) client = Client(url) print(client) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: url = 'http://arcweb.esri.com/services/v2/MapImage.wsdl' start(url) client = Client(url) print(client) env = client.factory.create('ns2:Envelope') print(env) options = client.factory.create('ns4:MapImageOptions') print(options) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: url = "http://www.thomas-bayer.com/axis2/services/BLZService?wsdl" start(url) client = Client(url) print(client) #client.setport(0) print(client.service.getBank("76251020")) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: url = "http://arcweb.esri.com/services/v2/RouteFinder.wsdl" start(url) client = Client(url) print(client) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() timer = metrics.Timer() try: url = "https://www.e-conomic.com/secure/api1/EconomicWebService.asmx?WSDL" start(url) timer.start() client = Client(url) #client.setport(0) timer.stop() print('create client: %s' % (timer,)) timer.start() s = str(client) timer.stop() print('str(client): %s' % (timer,)) print('client:\n%s' % (s,)) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() print('\nFinished: errors = %d' % (errors,)) suds-1.1.2/tests/external/rhq.py000066400000000000000000000143261425611400200166220ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) # # This test requires installation or visibility of an RHQ server. # ( http://www.rhq-project.org ) # import sys sys.path.append('../../') from suds import WebFault from suds.client import Client import traceback as tb errors = 0 def start(url): global errors print('\n______________________________________________________________\n') print('Test @ ( %s ) %d' % (url, errors)) def rhqTest(): global errors url = 'http://localhost.localdomain:7080/rhq-rhq-enterprise-server-ejb3/WebservicesManagerBean?wsdl' start(url) client = Client(url) print(client) try: # # create name # name = client.factory.create('name') name.first = u'Jeff'+unichr(1234) name.last = 'Ortel &lt; Company' # # create a phone object using the wsdl # phoneA = client.factory.create('phone') phoneA.npa = 410 phoneA.nxx = 555 phoneA.number = 5138 phoneB = client.factory.create('phone') phoneB.npa = 919 phoneB.nxx = 555 phoneB.number = 4406 # # lets add some animals # dog = client.factory.create('dog') dog.name = 'rover' dog.age = 3 cat = client.factory.create('cat') cat.name = 'kitty' cat.age = 4 # # create a person object using the wsdl # person = client.factory.create('person') print(person) person.name = name person.age = 43 person.phone.append(phoneA) person.phone.append(phoneB) person.pet.append(dog) person.pet.append(cat) print(person) # # addPerson() # print('addPersion()') result = client.service.addPerson(person) sent = client.last_sent() rcvd = client.last_received() print('\nreply(\n%s\n)\n' % (result,)) # # create a new name object used to update the person # newname = client.factory.create('name') newname.first = 'Todd' newname.last = None # # update the person's name (using the webservice) # print('updatePersion()') result = client.service.updatePerson(person, newname) print('\nreply(\n%s\n)\n' % (str(result),)) result = client.service.updatePerson(person, None) print('\nreply(\n%s\n)\n' % (str(result),)) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: print("echo('this is cool')") result = client.service.echo('this is cool') print('\nreply( %s )\n' % (str(result),)) print('echo(None)') result = client.service.echo(None) print('\nreply( %s )\n' % (str(result),)) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: print('hello()') result = client.service.hello() print('\nreply( %s )\n' % (str(result),)) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: print('testVoid()') result = client.service.testVoid() print('\nreply( %s )\n' % (str(result),)) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: mylist = ['my', 'dog', 'likes', 'steak'] print('testListArgs(%s)' % (mylist,)) result = client.service.testListArg(mylist) print('\nreply( %s )\n' % (str(result),)) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: s = 'hello' for n in range(0, 3): print('getList(%s, %d)' % (s, n)) result = client.service.getList(s, n) print('\nreply( %s )\n' % (str(result),)) if len(result) != n: errors += 1 print('expected (%d), reply (%d)' % (n, len(result))) except WebFault as f: errors += 1 print(f) print(f.fault) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() try: print('testExceptions()') result = client.service.testExceptions() print('\nreply( %s )\n' % (tostr(result),)) raise Exception('Fault expected and not raised') except WebFault as f: print(f) print(f.fault) print(f.document) except (KeyboardInterrupt, SystemExit): raise except Exception: errors += 1 print(sys.exc_info()[1]) tb.print_exc() if __name__ == '__main__': errors = 0 rhqTest() print('\nFinished: errors=%d' % (errors,)) suds-1.1.2/tests/external/saxenc.py000066400000000000000000000035361425611400200173120ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify # it under the terms of the (LGPL) GNU Lesser General Public License as # published by the Free Software Foundation; either version 3 of the # License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library Lesser General Public License for more details at # ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) # # sax encoding/decoding test. # from suds.sax.element import Element from suds.sax.parser import Parser def basic(): xml = "Me && <b>my</b> shadow's <i>dog</i> love to 'play' and sing "la,la,la";" p = Parser() d = p.parse(string=xml) a = d.root() print 'A(parsed)=\n%s' % a assert str(a) == xml b = Element('a') b.setText('Me && <b>my shadow\'s dog love to \'play\' and sing "la,la,la";') print 'B(encoded)=\n%s' % b assert str(b) == xml print 'A(text-decoded)=\n%s' % a.getText() print 'B(text-decoded)=\n%s' % b.getText() assert a.getText() == b.getText() print 'test pruning' j = Element('A') j.set('n', 1) j.append(Element('B')) print j j.prune() print j def cdata(): xml = 'This is my &<tag>]]>' p = Parser() d = p.parse(string=xml) print d a = d.root() print a.getText() if __name__ == '__main__': #basic() cdata() suds-1.1.2/tests/profiling/000077500000000000000000000000001425611400200156175ustar00rootroot00000000000000suds-1.1.2/tests/profiling/__init__.py000066400000000000000000000053221425611400200177320ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Package containing different suds profiling scripts used for comparing different suds detail implementations or running them under different Python interpreter versions. """ import sys import timeit class ProfilerBase(object): """Base class for profilers implemented in this project.""" def __init__(self, show_each_timing=False, show_minimum=True): self.show_each_timing = show_each_timing self.show_minimum = show_minimum def timeit(self, method_name, number, repeat=5): print("Timing '%s' (looping %d times, %d timings)" % (method_name, number, repeat)) # We want to pass 'self' as input to the timing functionality # implemented in the timeit module, but its interface only allows # sharing data via global storage. timit_input_storage = "__temporary_timeit_input_data_storage__12345" assert not hasattr(sys, timit_input_storage) try: code = "p.%s()" % (method_name,) setup_code = "import sys;p = sys.%s" % (timit_input_storage,) setattr(sys, timit_input_storage, self) timer = timeit.Timer(code, setup=setup_code) times = [] try: for i in range(repeat): time = timer.timeit(number) times.append(time) if self.show_each_timing: print("%d. %s" % (time,)) except (KeyboardInterrupt, SystemExit): raise except Exception: timer.print_exc() else: if self.show_minimum: print(min(times)) return times finally: try: delattr(sys, timit_input_storage) except AttributeError: pass suds-1.1.2/tests/profiling/profile_sax_encoder.py000066400000000000000000000106151425611400200222060ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Suds SAX module's special character encoder profiler. """ import suds.sax.enc import tests.profiling import sys """Profiler parameter constants.""" NONE = 0 SOME = 1 MANY = 2 QUANTITY = {NONE: "NONE", SOME: "SOME", MANY: "MANY"} class Profiler(tests.profiling.ProfilerBase): def __init__(self, long_input, replacements, cdata, show_each_timing=False, show_minimum=True): assert long_input in (True, False) assert replacements in QUANTITY assert cdata in QUANTITY super(Profiler, self).__init__(show_each_timing, show_minimum) self.encode_input = self.__construct_input(long_input, replacements, cdata, encoded=False) self.decode_input = self.__construct_input(long_input, replacements, cdata, encoded=True) print("long_input=%s; replacements=%s; cdata=%s" % (long_input, QUANTITY[replacements], QUANTITY[cdata])) print(" encode input data length: %d" % (len(self.encode_input),)) print(" decode input data length: %d" % (len(self.decode_input),)) def decode(self): suds.sax.enc.Encoder().decode(self.decode_input) def encode(self): suds.sax.enc.Encoder().encode(self.encode_input) def __construct_input(self, long_input, replacements, cdata, encoded): """Construct profiling input data matching the given parameters.""" basic_input = "All that glitters is not gold." replacements_input = "<>&'" if encoded: replacements_input = "><&&apos" cdata_input = "" # Approximate constructed input data size in bytes. size = 500 if long_input: size = 1000000 border_input = basic_input if replacements != NONE: border_input += replacements_input if cdata != NONE: border_input += cdata_input if replacements == MANY: basic_input += replacements_input if cdata == MANY: basic_input += cdata_input border_size = 2 * len(border_input) middle_size = max(size - border_size, 0) middle_count = middle_size // len(basic_input) + 1 input = [border_input, basic_input * middle_count, border_input] return "".join(input) if __name__ == "__main__": print("Python %s" % (sys.version,)) print("") p = Profiler(long_input=True, replacements=MANY, cdata=MANY) p.timeit('encode', 18) p.timeit('decode', 170) print("") p = Profiler(long_input=False, replacements=MANY, cdata=MANY) p.timeit('encode', 24000) p.timeit('decode', 140000) print("") p = Profiler(long_input=True, replacements=MANY, cdata=NONE) p.timeit('encode', 16) p.timeit('decode', 150) print("") p = Profiler(long_input=False, replacements=MANY, cdata=NONE) p.timeit('encode', 21000) p.timeit('decode', 140000) print("") p = Profiler(long_input=True, replacements=NONE, cdata=NONE) p.timeit('encode', 250) p.timeit('decode', 450) print("") p = Profiler(long_input=False, replacements=NONE, cdata=NONE) p.timeit('encode', 240000) p.timeit('decode', 200000) print("") p = Profiler(long_input=True, replacements=NONE, cdata=MANY) p.timeit('encode', 45) p.timeit('decode', 400) print("") p = Profiler(long_input=False, replacements=NONE, cdata=MANY) p.timeit('encode', 35000) p.timeit('decode', 190000) suds-1.1.2/tests/test_argument_parser.py000066400000000000000000000527541425611400200204520ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Suds Python library web service operation argument parser related unit tests. Suds library prepares web service operation invocation functions that construct actual web service operation invocation requests based on the parameters they receive and their web service operation's definition. The module tested here implements generic argument parsing and validation, not specific to a particular web service operation binding. """ import testutils if __name__ == "__main__": testutils.run_using_pytest(globals()) import suds import suds.argparser import pytest class MockAncestor: """ Represents a web service operation parameter ancestry item. Implements parts of the suds library's web service operation ancestry item interface required by the argument parser functionality. """ def __init__(self, is_choice=False): self.__is_choice = is_choice def choice(self): return self.__is_choice class MockParamProcessor: """ Mock parameter processor that gets passed argument parsing results. Collects received parameter information so it may be checked after argument parsing has completed. """ def __init__(self): self.params_ = [] def params(self): return self.params_ def process(self, param_name, param_type, in_choice_context, value): self.params_.append((param_name, param_type, in_choice_context, value)) class MockParamType: """ Represents a web service operation parameter type. Implements parts of the suds library's web service operation parameter type interface required by the argument parsing implementation tested in this module. """ def __init__(self, optional): self.optional_ = optional def optional(self): return self.optional_ @pytest.mark.parametrize("binding_style", ( "document", #TODO: Suds library's RPC binding implementation should be updated to use # the argument parsing functionality. This will remove code duplication # between different binding implementations and make their features more # balanced. pytest.param("rpc", marks=pytest.mark.xfail(reason="Not yet implemented.")), )) def test_binding_uses_argument_parsing(monkeypatch, binding_style): """ Calling web service operations should use the generic argument parsing functionality independent of the operation's specific binding style. """ class MyException(Exception): pass def raise_exception(*args, **kwargs): raise MyException monkeypatch.setattr(suds.argparser._ArgParser, "__init__", raise_exception) wsdl = suds.byte_str("""\ " """ % (binding_style,)) client = testutils.client_from_wsdl(wsdl, nosend=True, prettyxml=True) pytest.raises(MyException, client.service.f) pytest.raises(MyException, client.service.f, "x") pytest.raises(MyException, client.service.f, "x", "y") @pytest.mark.parametrize("binding_style", ( "document", #TODO: Suds library's RPC binding implementation should be updated to use # the argument parsing functionality. This will remove code duplication # between different binding implementations and make their features more # balanced. pytest.param("rpc", marks=pytest.mark.xfail(reason="Not yet implemented.")), )) def test_binding_for_an_operation_with_no_input_uses_argument_parsing( monkeypatch, binding_style): """ Calling web service operations should use the generic argument parsing functionality independent of the operation's specific binding style. """ class MyException(Exception): pass def raise_exception(*args, **kwargs): raise MyException monkeypatch.setattr(suds.argparser._ArgParser, "__init__", raise_exception) wsdl = suds.byte_str("""\ """ % (binding_style,)) client = testutils.client_from_wsdl(wsdl, nosend=True, prettyxml=True) pytest.raises(MyException, client.service.f) pytest.raises(MyException, client.service.f, "x") pytest.raises(MyException, client.service.f, "x", "y") @pytest.mark.parametrize(("param_optional", "args"), ( # Operations taking no parameters. ((), (1,)), ((), (1, 2)), ((), (1, 2, None)), # Operations taking a single parameter. ((True,), (1, 2)), ((False,), (1, 2)), ((True,), ("2", 2, None)), ((False,), ("2", 2, None)), ((True,), (object(), 2, None, None)), ((False,), (object(), 2, None, None)), ((True,), (None, 2, None, None, "5")), ((False,), (None, 2, None, None, "5")), # Operations taking multiple parameters. ((True, True), (1, 2, 3)), ((False, True), (1, 2, 3)), ((True, False), (1, 2, 3)), ((False, False), (1, 2, 3)), ((False, True), ("2", 2, None)), ((False, False), ("2", 2, None)), ((True, True), ("2", 2, None)), ((True, True, True), (object(), 2, None, None)), ((False, False, False), (object(), 2, None, None)), ((True, False, False), (None, 2, None, None, "5")), ((True, False, True), (None, 2, None, None, "5")), ((True, True, True), (None, 2, None, None, "5")))) def test_extra_positional_arguments(param_optional, args): """ Test passing extra positional arguments for an operation expecting more than one. """ param_count = len(param_optional) params = [] expected_args_min = 0 for i, optional in enumerate(param_optional): if not optional: expected_args_min += 1 param_name = "p%d" % (i,) param_type = MockParamType(optional) params.append((param_name, param_type)) param_processor = MockParamProcessor() takes_plural_suffix = "s" if expected_args_min == param_count: takes = param_count if param_count == 1: takes_plural_suffix = "" else: takes = "%d to %d" % (expected_args_min, param_count) was_were = "were" if len(args) == 1: was_were = "was" expected = "fru-fru() takes %s positional argument%s but %d %s given" % ( takes, takes_plural_suffix, len(args), was_were) _expect_error(TypeError, expected, suds.argparser.parse_args, "fru-fru", params, args, {}, param_processor.process, True) assert len(param_processor.params()) == param_count processed_params = param_processor.params() for expected_param, param, value in zip(params, processed_params, args): assert param[0] is expected_param[0] assert param[1] is expected_param[1] assert not param[2] assert param[3] is value @pytest.mark.parametrize(("param_names", "args", "kwargs"), ( (["a"], (1,), {"a": 5}), ([["a"]], (1,), {"a": 5}), (["a"], (None, 1, 2, 7), {"a": 5}), ([["a"]], (None, 1, 2, 7), {"a": 5}), (["a", ["b"], "c"], (None, None, None), {"a": 1, "b": 2, "c": 3}), ([["a"], ["b"], ["c"]], (None, None, None), {"a": 1, "b": 2, "c": 3}), (["a"], ("x",), {"a": None}), (["a", ["b"], ["c"]], (1,), {"a": None}), (["a", "b", ["c"]], (None, 2), {"b": None}))) def test_multiple_value_for_single_parameter_error(param_names, args, kwargs): """ Test how multiple value for a single parameter errors are reported. This report takes precedence over any extra positional argument errors. Optional parameters are marked by specifying their names as single element lists or tuples. """ params = [] duplicates = [] args_count = len(args) for n, param_name in enumerate(param_names): optional = False if param_name.__class__ in (tuple, list): optional = True param_name = param_name[0] if n < args_count and param_name in kwargs: duplicates.append(param_name) params.append((param_name, MockParamType(optional))) message = "q() got multiple values for parameter '%s'" expected = [message % (x,) for x in duplicates] if len(expected) == 1: expected = expected[0] _expect_error(TypeError, expected, suds.argparser.parse_args, "q", params, args, kwargs, _do_nothing, True) def test_not_reporting_extra_argument_errors(): """ When ArgParser has been configured not to report extra argument errors as exceptions, it should simply ignore any such extra arguments. This matches the suds library behaviour from before extra argument error reporting was implemented. """ x = MockAncestor() c = MockAncestor(is_choice=True) params = [ ("p1", MockParamType(False), [x]), ("p2", MockParamType(True), [x, c]), ("p3", MockParamType(False), [x, c])] args = list(range(5)) kwargs = {"p1": "p1", "p3": "p3", "x": 666} param_processor = MockParamProcessor() args_required, args_allowed = suds.argparser.parse_args("w", params, args, kwargs, param_processor.process, False) assert args_required == 1 assert args_allowed == 3 processed_params = param_processor.params() assert len(processed_params) == len(params) for expected_param, param, value in zip(params, processed_params, args): assert param[0] is expected_param[0] assert param[1] is expected_param[1] assert param[2] == (c in expected_param[2]) assert param[3] is value @pytest.mark.parametrize(("param_names", "args", "kwargs"), ( ([], (), {"x": 5}), ([], (None, 1, 2, 7), {"x": 5}), ([], (), {"x": 1, "y": 2, "z": 3}), (["a"], (), {"x": None}), ([["a"]], (), {"x": None}), (["a"], (1,), {"x": None}), ([["a"]], (1,), {"x": None}), (["a"], (), {"a": "spank me", "x": 5}), (["a"], (), {"x": 5, "a": "spank me"}), (["a"], (), {"a": "spank me", "x": 5, "wuwu": None}), (["a", "b", "c"], (1, 2), {"w": 666}), (["a", ["b"], ["c"]], (1,), {"c": None, "w": 666}), (["a", "b", ["c"]], (None,), {"b": None, "_": 666}))) def test_unexpected_keyword_argument(param_names, args, kwargs): """ Test how unexpected keyword arguments are reported. This report takes precedence over any extra positional argument errors. Optional parameters are marked by specifying their names as single element lists or tuples. """ params = [] arg_count = len(args) for n, param_name in enumerate(param_names): optional = False if param_name.__class__ in (tuple, list): optional = True param_name = param_name[0] if n < arg_count: assert param_name not in kwargs else: kwargs.pop(param_name, None) params.append((param_name, MockParamType(optional))) message = "pUFf() got an unexpected keyword argument '%s'" expected = [message % (x,) for x in kwargs] if len(expected) == 1: expected = expected[0] _expect_error(TypeError, expected, suds.argparser.parse_args, "pUFf", params, args, kwargs, _do_nothing, True) @pytest.mark.parametrize(("expect_required", "expect_allowed", "param_defs"), ( # No parameters. (0, 0, []), # Single parameter. (1, 1, [("p1", False, [1, 2, 3, 4])]), (0, 1, [("p1", True, [1, 2, 3, 4])]), (1, 1, [("p1", False, [1, 2, 3, [4]])]), (0, 1, [("p1", True, [1, 2, 3, [4]])]), (1, 1, [("p1", False, [1, [2], 3, 4])]), (0, 1, [("p1", True, [1, [2], 3, 4])]), (1, 1, [("p1", False, [1, [2], 3, [4]])]), (0, 1, [("p1", True, [1, [2], 3, [4]])]), # Multiple parameters. (4, 4, [ ("a", False, [1]), ("b", False, [1]), ("c", False, [1]), ("d", False, [1])]), (0, 4, [ ("a", True, [1]), ("b", True, [1]), ("c", True, [1]), ("d", True, [1])]), (2, 4, [ ("a", True, [1]), ("b", False, [1]), ("c", True, [1]), ("d", False, [1])]), (2, 4, [ ("a", False, [1]), ("b", True, [1]), ("c", False, [1]), ("d", True, [1])]), (3, 4, [ ("a", False, [1]), ("b", False, [1]), ("c", False, [1]), ("d", True, [1])]), (3, 4, [ ("a", True, [1]), ("b", False, [1]), ("c", False, [1]), ("d", False, [1])]), # Choice containing only simple members. (1, 2, [ ("a", False, [[1]]), ("b", False, [[1]])]), (0, 2, [ ("a", True, [[1]]), ("b", False, [[1]])]), (0, 2, [ ("a", False, [[1]]), ("b", True, [[1]])]), (0, 2, [ ("a", True, [[1]]), ("b", True, [[1]])]), # Choice containing a non-empty sequence. (1, 3, [ ("a", False, [1, 2, 3, [4]]), ("b1", False, [1, 2, 3, [4], 5]), ("b2", False, [1, 2, 3, [4], 5])]), # Choice with more than one required parameter. (2, 4, [ ("a1", False, [[1], 2]), ("a2", False, [[1], 2]), ("b1", False, [[1], 3]), ("b2", False, [[1], 3])]), (2, 5, [ ("a1", False, [[1], 2]), ("a2", False, [[1], 2]), ("b1", False, [[1], 3]), ("b2", False, [[1], 3]), ("b3", False, [[1], 3])]), (2, 5, [ ("a1", False, [[1], 2]), ("a2", False, [[1], 2]), ("a3", False, [[1], 2]), ("b1", False, [[1], 3]), ("b2", False, [[1], 3])]), (3, 6, [ ("a1", False, [[1], 2]), ("a2", False, [[1], 2]), ("a3", False, [[1], 2]), ("b1", False, [[1], 3]), ("b2", False, [[1], 3]), ("b3", False, [[1], 3])]), (2, 6, [ ("a1", False, [[1], 2]), ("a2", True, [[1], 2]), ("a3", False, [[1], 2]), ("b1", False, [[1], 3]), ("b2", False, [[1], 3]), ("b3", False, [[1], 3])]), # Sequence containing multiple choices. (2, 4, [ ("a1", False, [0, [1]]), ("a2", False, [0, [1]]), ("b1", False, [0, [2]]), ("b2", False, [0, [2]])]), (1, 4, [ ("a1", False, [0, [1]]), ("a2", False, [0, [1]]), ("b1", False, [0, [2]]), ("b2", True, [0, [2]])]), (3, 5, [ ("a1", False, [0, [1]]), ("a2", False, [0, [1]]), ("x", False, [0]), ("b1", False, [0, [2]]), ("b2", False, [0, [2]])]), # Choice containing optional parameters. (0, 3, [ ("a", False, [1, [2]]), ("b", True, [1, [2]]), ("c", False, [1, [2]])]), (0, 3, [ ("a", False, [1, [2]]), ("b1", True, [1, [2], 3]), ("b2", True, [1, [2], 3])]), (1, 3, [ ("a", False, [1, [2]]), ("b1", False, [1, [2], 3]), ("b2", True, [1, [2], 3])]), # Choices within choices next to choices. (3, 14, [ ("p01", False, [1]), ("p02", False, [1, [2], 3]), ("p03", False, [1, [2], 3]), ("p04", False, [1, [2], 4, 5, 6]), ("p05", False, [1, [2], 4, 5, 6, [7]]), ("p06", False, [1, [2], 4, 5, 6, [7], [8]]), ("p07", False, [1, [2], 4, 5, 6, [7], 9]), ("p08", False, [1, [2], 4, 5, 6, [7], 9]), ("p09", False, [1, [2], 4, [10], 11]), ("p10", False, [1, [2], 4, [10], 11]), ("p11", False, [1, [2], 4, [10], 12]), ("p12", False, [1, [2], 4, [10], 12]), ("p13", False, [1, [2], 4, [10], 12]), ("p14", False, [1, [2], 4, [13]])]), )) def test_unwrapped_arg_counts(expect_required, expect_allowed, param_defs): """ Test required & allowed argument count for unwrapped parameters. Expected 'param_defs' structure - list of 3-tuples containing the following: * Parameter name (string). * Optional (boolean). * Ancestry (list). * Contains integers and/or single element lists containing an integer. * Integers represent non-choice ancestry items. * Single element lists represent choice ancestry items. * Integer values represent ancestry item ids - different integer values represent separate ancestry items. """ ancestor_map = {} params = [] for param_name, param_optional, param_ancestry_def in param_defs: ancestry = [] for n, id in enumerate(param_ancestry_def): is_choice = False if id.__class__ is list: assert len(id) == 1, "bad test input" id = id[0] is_choice = True try: ancestor, ancestry_def = ancestor_map[id] except KeyError: ancestor = MockAncestor(is_choice) ancestor_map[id] = (ancestor, param_ancestry_def[:n]) else: assert ancestor.choice() == is_choice, "bad test input" assert ancestry_def == param_ancestry_def[:n], "bad test input" ancestry.append(ancestor) params.append((param_name, MockParamType(param_optional), ancestry)) param_processor = MockParamProcessor() args = [object() for x in params] args_required, args_allowed = suds.argparser.parse_args("w", params, args, {}, param_processor.process, False) assert args_required == expect_required assert args_allowed == expect_allowed processed_params = param_processor.params() assert len(processed_params) == len(params) for expected_param, param, value in zip(params, processed_params, args): assert param[0] is expected_param[0] assert param[1] is expected_param[1] expected_in_choice_context = False for x in expected_param[2]: if x.choice(): expected_in_choice_context = True break assert param[2] == expected_in_choice_context assert param[3] is value def _do_nothing(*args, **kwargs): """Do-nothing function used as a callback where needed during testing.""" pass def _expect_error(expected_exception, expected_error_text, test_function, *args, **kwargs): """ Assert a test function call raises an expected exception. Caught exception is considered expected if its string representation matches the given expected error text. Expected error text may be given directly or as a list/tuple containing valid alternatives. """ e = pytest.raises(expected_exception, test_function, *args, **kwargs).value try: if expected_error_text.__class__ in (list, tuple): assert str(e) in expected_error_text else: assert str(e) == expected_error_text finally: del e # explicitly break circular reference chain in Python 3 suds-1.1.2/tests/test_cache.py000066400000000000000000001155761425611400200163210ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Suds Python library document caching unit tests. Implemented using the 'pytest' testing framework. """ import testutils if __name__ == "__main__": testutils.run_using_pytest(globals()) import suds import suds.cache import suds.sax.parser import pytest from six import b, next, u import datetime import os import os.path import sys class MyException(Exception): """Local exception class used in the tests in this module.""" pass class InvisibleMan: """Dummy class used for pickling related tests.""" def __init__(self, x): self.x = x class MockDateTime(datetime.datetime): """ MockDateTime class monkeypatched to replace datetime.datetime. Allows us to control the exact built-in datetime.datetime.now() return value. Note that Python does not allow us to monkeypatch datetime.datetime.now() directly as it is a built-in function. """ mock_counter = 0 @staticmethod def now(*args, **kwargs): MockDateTime.mock_counter += 1 return MockDateTime.mock_value class MockFile: """ Wrapper around a regular file object allowing controlled file operation failures. """ def __init__(self, opener, file, fail_read): self.__opener = opener self.__file = file self.__fail_read = fail_read def __getattr__(self, *args, **kwargs): return getattr(self.__file, *args, **kwargs) def read(self, *args, **kwargs): self.__opener.read_counter += 1 if self.__fail_read: raise MyException return self.__file.read(*args, **kwargs) class MockFileOpener: """ Mock open() function for the suds.cache module. May cause such calls to fail or to return our MockFile objects prepared so some of their functions fail in a controlled manner. """ def __init__(self, fail_open=False, fail_read=False): self.__previous = None self.__fail_open = fail_open self.__fail_read = fail_read self.counter = 0 self.read_counter = 0 def __call__(self, *args, **kwargs): self.counter += 1 if self.__fail_open: raise MyException file = self.__previous(*args, **kwargs) return MockFile(self, file, fail_read=self.__fail_read) def apply(self, monkeypatch): """Monkeypatch suds.cache module's open() global.""" try: self.__previous = suds.cache.open except AttributeError: self.__previous = open monkeypatch.setitem(suds.cache.__dict__, "open", self) def reset(self): self.counter = 0 self.read_counter = 0 class MockParse: """Mock object causing suds.sax.parser.Parser.parse() failures.""" def __init__(self): self.counter = 0 def __call__(self, *args, **kwargs): self.counter += 1 raise MyException def apply(self, monkeypatch): """Monkeypatch suds SAX Parser's parse() method.""" monkeypatch.setattr(suds.sax.parser.Parser, "parse", self) def reset(self): self.counter = 0 class MockPickleLoad: """Mock object causing suds.cache module's pickle load failures.""" def __init__(self): self.counter = 0 def __call__(self, *args, **kwargs): self.counter += 1 raise MyException def apply(self, monkeypatch): """Monkeypatch suds.cache module's pickle.load().""" monkeypatch.setattr(suds.cache.pickle, "load", self) def reset(self): self.counter = 0 # Hardcoded values used in different caching test cases. value_empty = b("") value_f2 = b("fifi2") value_f22 = b("fifi22") value_f3 = b("fifi3") value_p1 = b("pero1") value_p11 = b("pero11") value_p111 = b("pero111") value_p2 = b("pero2") value_p22 = b("pero22") value_unicode = u("\u20AC \u7684 " "\u010D\u0107\u017E\u0161\u0111" "\u010C\u0106\u017D\u0160\u0110").encode("utf-8") # FileCache item expiration test data - duration, current_time, expect_remove. # Reused for different testing different FileCache derived classes. file_cache_item_expiration_test_data = ([ # Infinite cache entry durations. ({}, datetime.datetime.min, False), ({}, datetime.timedelta(days=-21), False), ({}, -datetime.datetime.resolution, False), ({}, datetime.timedelta(), False), ({}, datetime.datetime.resolution, False), ({}, datetime.timedelta(days=7), False), ({}, datetime.datetime.max, False)] + # Finite cache entry durations. [(duration, current_time, expect_remove) for duration in ( {"minutes": 7}, {"microseconds": 1}, {"microseconds": -1}, {"hours": -7}) for current_time, expect_remove in ( (datetime.datetime.min, False), (datetime.timedelta(days=-21), False), (-datetime.datetime.resolution, False), (datetime.timedelta(), False), (datetime.datetime.resolution, True), (datetime.timedelta(days=7), True), (datetime.datetime.max, True))]) @pytest.mark.parametrize(("method_name", "params"), ( ("clear", []), ("get", ["id"]), ("purge", ["id"]), ("put", ["id", "object"]))) def test_Cache_methods_abstract(monkeypatch, method_name, params): monkeypatch.delitem(locals(), "e", False) cache = suds.cache.Cache() f = getattr(cache, method_name) e = pytest.raises(Exception, f, *params).value try: assert e.__class__ is Exception assert str(e) == "not-implemented" finally: del e # explicitly break circular reference chain in Python 3 class TestDefaultFileCacheLocation: """Default FileCache cache location handling tests.""" @pytest.mark.parametrize("cache_class", ( suds.cache.DocumentCache, suds.cache.FileCache, suds.cache.ObjectCache)) def test_basic(self, tmpdir, cache_class): """ Test default FileCache folder usage. Initial DocumentCache/FileCache/ObjectCache instantiation with no explicitly specified location in a process should use tempfile.mkdtemp() and that folder should be used as its location. After a single DocumentCache/FileCache/ObjectCache instantiation with no explicitly specified location, all later DocumentCache/FileCache/ ObjectCache instantiations with no explicitly specified location in the same process should use that same location folder without additional tempfile.mkdtemp() calls. Both initial & non-initial DocumentCache/FileCache/ObjectCache instantiation with an explicitly specified location should use that folder as its default location and not make any tempfile.mkdtemp() calls. """ cache_folder_name = "my test cache-%s" % (cache_class.__name__,) cache_folder = tmpdir.join(cache_folder_name).strpath fake_cache_folder_name = "my fake cache-%s" % (cache_class.__name__,) fake_cache_folder = tmpdir.join(fake_cache_folder_name).strpath test_file = tmpdir.join("test_file.py") test_file.write("""\ import os.path import tempfile original_mkdtemp = tempfile.mkdtemp mock_mkdtemp_counter = 0 def mock_mkdtemp(*args, **kwargs): global mock_mkdtemp_counter mock_mkdtemp_counter += 1 return cache_folder tempfile.mkdtemp = mock_mkdtemp def check_cache_folder(expected_exists, expected_mkdtemp_counter, comment): if os.path.exists(cache_folder) != expected_exists: if expected_exists: message = "does not exist when expected" else: message = "exists when not expected" print("Cache folder %%s (%%s)." %% (message, comment)) sys.exit(-2) if mock_mkdtemp_counter != expected_mkdtemp_counter: if mock_mkdtemp_counter < expected_mkdtemp_counter: message = "less" else: message = "more" print("tempfile.mkdtemp() called %%s times then expected (%%s)" %% (message, comment,)) cache_folder = %(cache_folder)r fake_cache_folder = %(fake_cache_folder)r def fake_cache(n): return fake_cache_folder + str(n) from suds.cache import DocumentCache, FileCache, ObjectCache check_cache_folder(False, 0, "import") assert DocumentCache(fake_cache(1)).location == fake_cache(1) assert FileCache(fake_cache(2)).location == fake_cache(2) assert ObjectCache(fake_cache(3)).location == fake_cache(3) check_cache_folder(False, 0, "initial caches with non-default location") assert %(cache_class_name)s().location == cache_folder check_cache_folder(True, 1, "initial cache with default location") assert DocumentCache().location == cache_folder assert FileCache().location == cache_folder assert ObjectCache().location == cache_folder check_cache_folder(True, 1, "non-initial caches with default location") assert DocumentCache(fake_cache(4)).location == fake_cache(4) assert FileCache(fake_cache(5)).location == fake_cache(5) assert ObjectCache(fake_cache(6)).location == fake_cache(6) check_cache_folder(True, 1, "non-initial caches with non-default location") assert DocumentCache().location == cache_folder assert FileCache().location == cache_folder assert ObjectCache().location == cache_folder check_cache_folder(True, 1, "final caches with default location") """ % {"cache_class_name": cache_class.__name__, "cache_folder": cache_folder, "fake_cache_folder": fake_cache_folder}) assert not os.path.exists(cache_folder) testutils.run_test_process(test_file) @pytest.mark.parametrize("removal_enabled", (True, False)) def test_remove_on_exit(self, tmpdir, removal_enabled): """ Test removing the default cache folder on process exit. The folder should be removed by default on process exit, but this behaviour may be disabled by the user. """ cache_folder_name = "my test cache-%s" % (removal_enabled,) cache_folder = tmpdir.join(cache_folder_name).strpath test_file = tmpdir.join("test_file.py") test_file.write("""\ import os.path import tempfile original_mkdtemp = tempfile.mkdtemp mock_mkdtemp_counter = 0 def mock_mkdtemp(*args, **kwargs): global mock_mkdtemp_counter mock_mkdtemp_counter += 1 return cache_folder tempfile.mkdtemp = mock_mkdtemp import suds.cache if not suds.cache.FileCache.remove_default_location_on_exit: print("Default FileCache folder removal not enabled by default.") sys.exit(-2) suds.cache.FileCache.remove_default_location_on_exit = %(removal_enabled)s cache_folder = %(cache_folder)r if os.path.exists(cache_folder): print("Cache folder exists too early.") sys.exit(-2) suds.cache.FileCache() if not mock_mkdtemp_counter == 1: print("tempfile.mkdtemp() not called as expected (%%d)." %% (mock_mkdtemp_counter,)) sys.exit(-2) if not os.path.isdir(cache_folder): print("Cache folder not created when expected.") sys.exit(-2) """ % {"cache_folder": cache_folder, "removal_enabled": removal_enabled}) assert not os.path.exists(cache_folder) testutils.run_test_process(test_file) if removal_enabled: assert not os.path.exists(cache_folder) else: assert os.path.isdir(cache_folder) class TestDocumentCache: def compare_document_to_content(self, document, content): """Assert that the given XML document and content match.""" assert document.__class__ is suds.sax.document.Document elements = document.getChildren() assert len(elements) == 1 element = elements[0] assert element.__class__ is suds.sax.element.Element assert suds.byte_str(str(element)) == content @staticmethod def construct_XML(element_name="Elemento"): """ Construct XML content and a Document wrapping it. The XML contains a single Element (may be parametrized with the given element name) and possibly additional sub-elements under it. """ #TODO: Update the tests in this group to no longer depend on the exact # input XML data formatting. They currently expect it to be formatted # exactly as what gets read back from their DocumentCache. content = suds.byte_str("""\ """ % (element_name,)) xml = suds.sax.parser.Parser().parse(suds.BytesIO(content)) assert xml.__class__ is suds.sax.document.Document return content, xml def test_cache_document(self, tmpdir): cache_item_id = "unga1" cache = suds.cache.DocumentCache(tmpdir.strpath) assert isinstance(cache, suds.cache.FileCache) assert cache.get(cache_item_id) is None content, document = self.construct_XML() cache.put(cache_item_id, document) self.compare_document_to_content(cache.get(cache_item_id), content) def test_cache_element(self, tmpdir): cache_item_id = "unga1" cache = suds.cache.DocumentCache(tmpdir.strpath) assert isinstance(cache, suds.cache.FileCache) assert cache.get(cache_item_id) is None content, document = self.construct_XML() cache.put(cache_item_id, document.root()) self.compare_document_to_content(cache.get(cache_item_id), content) def test_file_open_failure(self, tmpdir, monkeypatch): """ File open failure should cause no cached object to be found, but any existing underlying cache file should be kept around. """ mock_open = MockFileOpener(fail_open=True) cache_folder = tmpdir.strpath cache = suds.cache.DocumentCache(cache_folder) content1, document1 = self.construct_XML("One") content2, document2 = self.construct_XML("Two") assert content1 != content2 cache.put("unga1", document1) mock_open.apply(monkeypatch) assert cache.get("unga1") is None monkeypatch.undo() assert mock_open.counter == 1 _assert_empty_cache_folder(cache_folder, expected=False) self.compare_document_to_content(cache.get("unga1"), content1) mock_open.apply(monkeypatch) assert cache.get("unga2") is None monkeypatch.undo() assert mock_open.counter == 2 _assert_empty_cache_folder(cache_folder, expected=False) self.compare_document_to_content(cache.get("unga1"), content1) assert cache.get("unga2") is None cache.put("unga2", document2) assert mock_open.counter == 2 mock_open.apply(monkeypatch) assert cache.get("unga1") is None monkeypatch.undo() assert mock_open.counter == 3 _assert_empty_cache_folder(cache_folder, expected=False) self.compare_document_to_content(cache.get("unga1"), content1) self.compare_document_to_content(cache.get("unga2"), content2) assert mock_open.counter == 3 @pytest.mark.parametrize(("mock", "extra_checks"), ( (MockParse(), [lambda x: True] * 4), (MockFileOpener(fail_read=True), [ lambda x: x.read_counter != 0, lambda x: x.read_counter == 0, lambda x: x.read_counter != 0, lambda x: x.read_counter == 0]))) def test_file_operation_failure(self, tmpdir, monkeypatch, mock, extra_checks): """ File operation failures such as reading failures or failing to parse data read from such a file should cause no cached object to be found and the related cache file to be removed. """ cache_folder = tmpdir.strpath cache = suds.cache.DocumentCache(cache_folder) content1, document1 = self.construct_XML("Eins") content2, document2 = self.construct_XML("Zwei") cache.put("unga1", document1) mock.apply(monkeypatch) assert cache.get("unga1") is None monkeypatch.undo() assert mock.counter == 1 assert extra_checks[0](mock) _assert_empty_cache_folder(cache_folder) mock.reset() assert cache.get("unga1") is None cache.put("unga1", document1) cache.put("unga2", document2) assert mock.counter == 0 assert extra_checks[1](mock) mock.reset() mock.apply(monkeypatch) assert cache.get("unga1") is None monkeypatch.undo() assert mock.counter == 1 assert extra_checks[2](mock) _assert_empty_cache_folder(cache_folder, expected=False) mock.reset() assert cache.get("unga1") is None self.compare_document_to_content(cache.get("unga2"), content2) assert mock.counter == 0 assert extra_checks[3](mock) @pytest.mark.parametrize(("duration", "current_time", "expect_remove"), file_cache_item_expiration_test_data) def test_item_expiration(self, tmpdir, monkeypatch, duration, current_time, expect_remove): """See TestFileCache.item_expiration_test_worker() for more info.""" cache = suds.cache.DocumentCache(tmpdir.strpath, **duration) content, document = self.construct_XML() cache.put("willy", document) TestFileCache.item_expiration_test_worker(cache, "willy", monkeypatch, current_time, expect_remove) def test_repeated_reads(self, tmpdir): cache = suds.cache.DocumentCache(tmpdir.strpath) content, document = self.construct_XML() cache.put("unga1", document) read_XML = cache.get("unga1").str() assert read_XML == cache.get("unga1").str() assert cache.get(None) is None assert cache.get("") is None assert cache.get("unga2") is None assert read_XML == cache.get("unga1").str() class TestFileCache: @staticmethod def item_expiration_test_worker(cache, id, monkeypatch, current_time, expect_remove): """ Test how a FileCache & its derived classes expire their item entries. Facts tested: * 0 duration should cause cache items never to expire. * Expired item files should be automatically removed from the cache folder. * Negative durations should be treated the same as positive ones. Requirements on the passed cache object: * Configures with the correct duration for this test. * Contains a valid cached item with the given id and its ctime timestamp + cache.duration must fall into the valid datetime.datetime value range. * Must use only public & protected FileCache interfaces to access its cache item data files. 'current_time' values are expected to be either datetime.datetime or datetime.timedelta instances with the latter interpreted relative to the test file's expected expiration time. """ assert isinstance(cache, suds.cache.FileCache) filepath = cache._FileCache__filename(id) assert os.path.isfile(filepath) file_timestamp = os.path.getctime(filepath) file_time = datetime.datetime.fromtimestamp(file_timestamp) MockDateTime.mock_counter = 0 if isinstance(current_time, datetime.timedelta): expire_time = file_time + cache.duration MockDateTime.mock_value = expire_time + current_time else: MockDateTime.mock_value = current_time monkeypatch.setattr(datetime, "datetime", MockDateTime) fp = cache._getf(id) assert (fp is None) == expect_remove if fp: fp.close() monkeypatch.undo() if cache.duration: assert MockDateTime.mock_counter == 1 else: assert MockDateTime.mock_counter == 0 assert os.path.isfile(filepath) == (not expect_remove) def test_basic_construction(self): cache = suds.cache.FileCache() assert isinstance(cache, suds.cache.Cache) assert cache.duration.__class__ is datetime.timedelta def test_cached_content_empty(self, tmpdir): cache_folder = tmpdir.strpath cache = suds.cache.FileCache(cache_folder) cache.put("unga1", value_empty) assert cache.get("unga1") == value_empty _assert_empty_cache_folder(cache_folder, expected=False) def test_cached_content_unicode(self, tmpdir): cache_folder = tmpdir.strpath cache = suds.cache.FileCache(cache_folder) cache.put("unga1", value_unicode) assert cache.get("unga1") == value_unicode _assert_empty_cache_folder(cache_folder, expected=False) def test_clear(self, tmpdir): cache_folder1 = tmpdir.join("fungus").strpath cache1 = suds.cache.FileCache(cache_folder1) cache1.put("unga1", value_p1) _assert_empty_cache_folder(cache_folder1, expected=False) cache1.put("unga2", value_p2) _assert_empty_cache_folder(cache_folder1, expected=False) assert cache1.get("unga1") == value_p1 assert cache1.get("unga2") == value_p2 _assert_empty_cache_folder(cache_folder1, expected=False) cache1.clear() _assert_empty_cache_folder(cache_folder1) assert cache1.get("unga1") is None assert cache1.get("unga2") is None _assert_empty_cache_folder(cache_folder1) cache1.put("unga1", value_p11) cache1.put("unga2", value_p2) _assert_empty_cache_folder(cache_folder1, expected=False) assert cache1.get("unga1") == value_p11 assert cache1.get("unga2") == value_p2 _assert_empty_cache_folder(cache_folder1, expected=False) cache_folder2 = tmpdir.join("broccoli").strpath cache2 = suds.cache.FileCache(cache_folder2) cache2.put("unga2", value_f2) assert cache2.get("unga2") == value_f2 assert cache1.get("unga2") == value_p2 cache2.clear() _assert_empty_cache_folder(cache_folder1, expected=False) _assert_empty_cache_folder(cache_folder2) assert cache2.get("unga2") is None assert cache1.get("unga1") == value_p11 assert cache1.get("unga2") == value_p2 cache2.put("unga2", value_p22) assert cache2.get("unga2") == value_p22 def test_close_leaves_cached_files_behind(self, tmpdir): cache_folder1 = tmpdir.join("ana").strpath cache1 = suds.cache.FileCache(cache_folder1) cache1.put("unga1", value_p1) cache1.put("unga2", value_p2) cache_folder2 = tmpdir.join("nan").strpath cache2 = suds.cache.FileCache(cache_folder2) cache2.put("unga2", value_f2) cache2.put("unga3", value_f3) del cache1 cache11 = suds.cache.FileCache(cache_folder1) assert cache11.get("unga1") == value_p1 assert cache11.get("unga2") == value_p2 assert cache2.get("unga2") == value_f2 assert cache2.get("unga3") == value_f3 def test_get_put(self, tmpdir): cache_folder1 = tmpdir.join("firefly").strpath cache1 = suds.cache.FileCache(cache_folder1) _assert_empty_cache_folder(cache_folder1) assert cache1.get("unga1") is None cache1.put("unga1", value_p1) _assert_empty_cache_folder(cache_folder1, expected=False) assert cache1.get("unga1") == value_p1 assert cache1.get("unga2") is None cache1.put("unga1", value_p11) assert cache1.get("unga1") == value_p11 assert cache1.get("unga2") is None cache1.put("unga2", value_p2) assert cache1.get("unga1") == value_p11 assert cache1.get("unga2") == value_p2 cache_folder2 = tmpdir.join("semper fi").strpath cache2 = suds.cache.FileCache(cache_folder2) _assert_empty_cache_folder(cache_folder2) assert cache2.get("unga2") is None cache2.put("unga2", value_f2) _assert_empty_cache_folder(cache_folder2, expected=False) assert cache2.get("unga2") == value_f2 assert cache2.get("unga3") is None cache2.put("unga2", value_f22) assert cache2.get("unga2") == value_f22 assert cache2.get("unga3") is None cache2.put("unga3", value_f3) assert cache2.get("unga2") == value_f22 assert cache2.get("unga3") == value_f3 _assert_empty_cache_folder(cache_folder1, expected=False) _assert_empty_cache_folder(cache_folder2, expected=False) assert cache1.get("unga1") == value_p11 assert cache1.get("unga2") == value_p2 assert cache1.get("unga3") is None assert cache2.get("unga1") is None assert cache2.get("unga2") == value_f22 assert cache2.get("unga3") == value_f3 def test_independent_item_expirations(self, tmpdir, monkeypatch): cache = suds.cache.FileCache(tmpdir.strpath, days=1) cache.put("unga1", value_p1) cache.put("unga2", value_p2) cache.put("unga3", value_f2) filepath1 = cache._FileCache__filename("unga1") filepath2 = cache._FileCache__filename("unga2") filepath3 = cache._FileCache__filename("unga3") file_timestamp1 = os.path.getctime(filepath1) file_timestamp2 = file_timestamp1 + 10 * 60 # in seconds file_timestamp3 = file_timestamp1 + 20 * 60 # in seconds file_time1 = datetime.datetime.fromtimestamp(file_timestamp1) file_time1_expiration = file_time1 + cache.duration original_getctime = os.path.getctime def mock_getctime(path): if path == filepath2: return file_timestamp2 if path == filepath3: return file_timestamp3 return original_getctime(path) timedelta = datetime.timedelta monkeypatch.setattr(os.path, "getctime", mock_getctime) monkeypatch.setattr(datetime, "datetime", MockDateTime) MockDateTime.mock_value = file_time1_expiration + timedelta(minutes=15) assert cache._getf("unga2") is None assert os.path.isfile(filepath1) assert not os.path.isfile(filepath2) assert os.path.isfile(filepath3) cache._getf("unga3").close() assert os.path.isfile(filepath1) assert not os.path.isfile(filepath2) assert os.path.isfile(filepath3) MockDateTime.mock_value = file_time1_expiration + timedelta(minutes=25) assert cache._getf("unga1") is None assert not os.path.isfile(filepath1) assert not os.path.isfile(filepath2) assert os.path.isfile(filepath3) assert cache._getf("unga3") is None assert not os.path.isfile(filepath1) assert not os.path.isfile(filepath2) assert not os.path.isfile(filepath3) @pytest.mark.parametrize(("duration", "current_time", "expect_remove"), file_cache_item_expiration_test_data) def test_item_expiration(self, tmpdir, monkeypatch, duration, current_time, expect_remove): """See TestFileCache.item_expiration_test_worker() for more info.""" cache = suds.cache.FileCache(tmpdir.strpath, **duration) cache.put("unga1", value_p1) TestFileCache.item_expiration_test_worker(cache, "unga1", monkeypatch, current_time, expect_remove) def test_non_default_location(self, tmpdir): FileCache = suds.cache.FileCache cache_folder1 = tmpdir.join("flip-flop1").strpath assert not os.path.isdir(cache_folder1) assert FileCache(location=cache_folder1).location == cache_folder1 _assert_empty_cache_folder(cache_folder1) cache_folder2 = tmpdir.join("flip-flop2").strpath assert not os.path.isdir(cache_folder2) assert FileCache(cache_folder2).location == cache_folder2 _assert_empty_cache_folder(cache_folder2) def test_purge(self, tmpdir): cache_folder1 = tmpdir.join("flamenco").strpath cache1 = suds.cache.FileCache(cache_folder1) cache1.put("unga1", value_p1) assert cache1.get("unga1") == value_p1 cache1.purge("unga1") _assert_empty_cache_folder(cache_folder1) assert cache1.get("unga1") is None cache1.put("unga1", value_p11) cache1.put("unga2", value_p2) assert cache1.get("unga1") == value_p11 assert cache1.get("unga2") == value_p2 cache1.purge("unga1") assert cache1.get("unga1") is None assert cache1.get("unga2") == value_p2 cache1.put("unga1", value_p111) cache_folder2 = tmpdir.join("shadow").strpath cache2 = suds.cache.FileCache(cache_folder2) cache2.put("unga2", value_f2) cache2.purge("unga2") _assert_empty_cache_folder(cache_folder2) assert cache1.get("unga1") == value_p111 assert cache1.get("unga2") == value_p2 assert cache2.get("unga2") is None def test_reused_cache_folder(self, tmpdir): cache_folder = tmpdir.strpath cache1 = suds.cache.FileCache(cache_folder) _assert_empty_cache_folder(cache_folder) assert cache1.get("unga1") is None cache1.put("unga1", value_p1) assert cache1.get("unga1") == value_p1 assert cache1.get("unga2") is None cache1.put("unga1", value_p11) assert cache1.get("unga1") == value_p11 assert cache1.get("unga2") is None cache1.put("unga2", value_p2) assert cache1.get("unga1") == value_p11 assert cache1.get("unga2") == value_p2 cache2 = suds.cache.FileCache(cache_folder) assert cache2.get("unga1") == value_p11 assert cache2.get("unga2") == value_p2 cache2.put("unga2", value_f2) cache2.put("unga3", value_f3) assert cache1.get("unga2") == value_f2 assert cache1.get("unga3") == value_f3 cache1.purge("unga2") assert cache2.get("unga2") is None cache1.clear() assert cache2.get("unga1") is None assert cache2.get("unga3") is None @pytest.mark.parametrize("params", ( {}, {"microseconds": 1}, {"milliseconds": 1}, {"seconds": 1}, {"minutes": 1}, {"hours": 1}, {"days": 1}, {"weeks": 1}, {"microseconds": -1}, {"milliseconds": -1}, {"seconds": -1}, {"minutes": -1}, {"hours": -1}, {"days": -1}, {"weeks": -1}, {"weeks": 1, "days": 2, "hours": 7, "minutes": 0, "seconds": -712})) def test_set_durations(self, tmpdir, params): cache = suds.cache.FileCache(tmpdir.strpath, **params) assert cache.duration == datetime.timedelta(**params) def test_version(self, tmpdir): fake_version_info = "--- fake version info ---" assert suds.__version__ != fake_version_info version_file = tmpdir.join("version") cache_folder = tmpdir.strpath cache = suds.cache.FileCache(cache_folder) assert version_file.read() == suds.__version__ cache.put("unga1", value_p1) version_file.write(fake_version_info) assert cache.get("unga1") == value_p1 cache2 = suds.cache.FileCache(cache_folder) _assert_empty_cache_folder(cache_folder) assert cache.get("unga1") is None assert cache2.get("unga1") is None assert version_file.read() == suds.__version__ cache.put("unga1", value_p11) cache.put("unga2", value_p22) version_file.remove() assert cache.get("unga1") == value_p11 assert cache.get("unga2") == value_p22 cache3 = suds.cache.FileCache(cache_folder) _assert_empty_cache_folder(cache_folder) assert cache.get("unga1") is None assert cache.get("unga2") is None assert cache2.get("unga1") is None assert cache3.get("unga1") is None assert version_file.read() == suds.__version__ def test_NoCache(monkeypatch): cache = suds.cache.NoCache() assert isinstance(cache, suds.cache.Cache) assert cache.get("id") == None cache.put("id", "something") assert cache.get("id") == None #TODO: It should not be an error to call clear() or purge() on a NoCache # instance. monkeypatch.delitem(locals(), "e", False) e = pytest.raises(Exception, cache.purge, "id").value try: assert str(e) == "not-implemented" finally: del e # explicitly break circular reference chain in Python 3 e = pytest.raises(Exception, cache.clear).value try: assert str(e) == "not-implemented" finally: del e # explicitly break circular reference chain in Python 3 class TestObjectCache: def test_basic(self, tmpdir): cache = suds.cache.ObjectCache(tmpdir.strpath) assert isinstance(cache, suds.cache.FileCache) assert cache.get("unga1") is None assert cache.get("unga2") is None cache.put("unga1", InvisibleMan(1)) cache.put("unga2", InvisibleMan(2)) read1 = cache.get("unga1") read2 = cache.get("unga2") assert read1.__class__ is InvisibleMan assert read2.__class__ is InvisibleMan assert read1.x == 1 assert read2.x == 2 def test_file_open_failure(self, tmpdir, monkeypatch): """ File open failure should cause no cached object to be found, but any existing underlying cache file should be kept around. """ mock_open = MockFileOpener(fail_open=True) cache_folder = tmpdir.strpath cache = suds.cache.ObjectCache(cache_folder) cache.put("unga1", InvisibleMan(1)) mock_open.apply(monkeypatch) assert cache.get("unga1") is None monkeypatch.undo() assert mock_open.counter == 1 _assert_empty_cache_folder(cache_folder, expected=False) assert cache.get("unga1").x == 1 mock_open.apply(monkeypatch) assert cache.get("unga2") is None monkeypatch.undo() assert mock_open.counter == 2 _assert_empty_cache_folder(cache_folder, expected=False) assert cache.get("unga1").x == 1 assert cache.get("unga2") is None cache.put("unga2", InvisibleMan(2)) assert mock_open.counter == 2 mock_open.apply(monkeypatch) assert cache.get("unga1") is None monkeypatch.undo() assert mock_open.counter == 3 _assert_empty_cache_folder(cache_folder, expected=False) assert cache.get("unga1").x == 1 assert cache.get("unga2").x == 2 assert mock_open.counter == 3 @pytest.mark.parametrize(("mock", "extra_checks"), ( (MockPickleLoad(), [lambda x: True] * 4), (MockFileOpener(fail_read=True), [ lambda x: x.read_counter != 0, lambda x: x.read_counter == 0, lambda x: x.read_counter != 0, lambda x: x.read_counter == 0]))) def test_file_operation_failure(self, tmpdir, monkeypatch, mock, extra_checks): """ Open file operation failures such as reading failures or failing to unpickle the data read from such a file should cause no cached object to be found and the related cache file to be removed. """ cache_folder = tmpdir.strpath cache = suds.cache.ObjectCache(cache_folder) cache.put("unga1", InvisibleMan(1)) mock.apply(monkeypatch) assert cache.get("unga1") is None monkeypatch.undo() assert mock.counter == 1 assert extra_checks[0](mock) _assert_empty_cache_folder(cache_folder) mock.reset() assert cache.get("unga1") is None cache.put("unga1", InvisibleMan(1)) cache.put("unga2", InvisibleMan(2)) assert mock.counter == 0 assert extra_checks[1](mock) mock.reset() mock.apply(monkeypatch) assert cache.get("unga1") is None monkeypatch.undo() assert mock.counter == 1 assert extra_checks[2](mock) _assert_empty_cache_folder(cache_folder, expected=False) mock.reset() assert cache.get("unga1") is None assert cache.get("unga2").x == 2 assert mock.counter == 0 assert extra_checks[3](mock) @pytest.mark.parametrize(("duration", "current_time", "expect_remove"), file_cache_item_expiration_test_data) def test_item_expiration(self, tmpdir, monkeypatch, duration, current_time, expect_remove): """See TestFileCache.item_expiration_test_worker() for more info.""" cache = suds.cache.ObjectCache(tmpdir.strpath, **duration) cache.put("silly", InvisibleMan(666)) TestFileCache.item_expiration_test_worker(cache, "silly", monkeypatch, current_time, expect_remove) def _assert_empty_cache_folder(folder, expected=True): """Test utility asserting that a cache folder is or is not empty.""" if not _is_assert_enabled(): return assert os.path.isdir(folder) def walk_error(error): pytest.fail("Error walking through cache folder content.") root, folders, files = next(os.walk(folder, onerror=walk_error)) assert root == folder empty = len(folders) == 0 and len(files) == 1 and files[0] == 'version' if expected: assert len(folders) == 0 assert len(files) == 1 assert files[0] == 'version' assert empty, "bad test code" else: assert not empty, "unexpected empty cache folder" def _is_assert_enabled(): """Return whether Python assertions have been enabled in this module.""" try: assert False except AssertionError: return True return False suds-1.1.2/tests/test_client.py000066400000000000000000001442531425611400200165260ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Suds Python library suds.client.Client related unit tests. Implemented using the 'pytest' testing framework. """ import testutils if __name__ == "__main__": testutils.run_using_pytest(globals()) import suds import suds.cache import suds.store import suds.transport import suds.transport.https import pytest from six import b, binary_type, iteritems, itervalues, next from six.moves import http_client class MyException(Exception): """Local exception class used in this testing module.""" pass class MockCache(suds.cache.Cache): """ Mock cache structure used in the tests in this module. Implements an in-memory cache and allows the test code to test the exact triggered cache operations. May be configured to allow or not adding additional entries to the cache, thus allowing our tests complete control over the cache's content. """ """Enumeration for specific mock operation configurations.""" ALLOW = 0 IGNORE = 1 FAIL = 2 def __init__(self): self.mock_data = {} self.mock_log = [] self.mock_put_config = MockCache.ALLOW super(MockCache, self).__init__() def clear(self): self.mock_log.append(("clear", [])) pytest.fail("Unexpected MockCache.clear() operation call.") def get(self, id): self.mock_log.append(("get", [id])) return self.mock_data.get(id, None) def purge(self, id): self.mock_log.append(("purge", [id])) pytest.fail("Unexpected MockCache.purge() operation call.") def put(self, id, object): self.mock_log.append(("put", [id, object])) if self.mock_put_config == MockCache.FAIL: pytest.fail("Unexpected MockCache.put() operation call.") if self.mock_put_config == MockCache.ALLOW: self.mock_data[id] = object else: assert self.mock_put_config == MockCache.IGNORE class MockDocumentStore(suds.store.DocumentStore): """Mock DocumentStore tracking all of its operations.""" def __init__(self, *args, **kwargs): self.mock_log = [] self.mock_fail = kwargs.pop("mock_fail", False) super(MockDocumentStore, self).__init__(*args, **kwargs) def open(self, url): self.mock_log.append(url) if self.mock_fail: raise MyException return super(MockDocumentStore, self).open(url) def reset(self): self.mock_log = [] class MockTransport(suds.transport.Transport): """ Mock Transport used by the tests implemented in this module. Allows the tests to check which transport operations got triggered and to control what each of them returns. open/send output data may be given either as a single value or a list of values to be used in order. Specifying a single value is a shortcut with the same semantics as specifying a single element list containing that value. Each of the value items may be either a simple byte string to be returned or an Exception subclass or instance indicating an exception to be raised from a particular operation call. """ def __init__(self, open_data=None, send_data=None): if open_data is None: open_data = [] elif open_data.__class__ is not list: open_data = [open_data] if send_data is None: send_data = [] elif send_data.__class__ is not list: send_data = [send_data] self.mock_log = [] self.mock_requests = [] self.mock_open_data = open_data self.mock_send_data = send_data super(MockTransport, self).__init__() def open(self, request): self.mock_log.append(("open", [request.url])) self.mock_requests.append(request) if not self.mock_open_data: pytest.fail("Unexpected MockTransport.open() operation call.") result = self.__next_operation_result(self.mock_open_data) return suds.BytesIO(result) def send(self, request): self.mock_log.append(("send", [request.url, request.message])) self.mock_requests.append(request) if not self.mock_send_data: pytest.fail("Unexpected MockTransport.send() operation call.") status = http_client.OK headers = {} data = self.__next_operation_result(self.mock_send_data) return suds.transport.Reply(status, headers, data) @staticmethod def __next_operation_result(data_list): value = data_list.pop(0) if isinstance(value, Exception): raise value if value.__class__ is type and issubclass(value, Exception): raise value() assert value.__class__ is binary_type, "bad test data" return value # Test data used in different tests in this module testing suds WSDL schema # import implementation. wsdl_imported_wsdl_namespace = "goodbye" def wsdl_imported_format(schema_content="", target_namespace=wsdl_imported_wsdl_namespace, target_xsd_namespace="ice-scream"): return b("""\ %(schema_content)s """ % dict(schema_content=schema_content, tns_xsd=target_xsd_namespace, tns=target_namespace)) def wsdl_import_wrapper_format(url_imported, imported_reference_ns=wsdl_imported_wsdl_namespace, target_namespace="hello"): return b("""\ """ % dict(imported_ns=wsdl_imported_wsdl_namespace, imported_reference_ns=imported_reference_ns, tns=target_namespace, url_imported=url_imported)) # Test URL data used by several tests in this test module. test_URL_data = ( "sudo://make-me-a-sammich", "http://my little URL", "https://my little URL", "xxx://my little URL", "xxx:my little URL", "xxx:") class TestCacheStoreTransportUsage: """ suds.client.Client cache/store/transport component usage interaction tests. """ class TestCachedWSDLObjectUsage: """ Using a WSDL object read from the cache should not attempt to fetch any of its referenced external documents from either the cache, the document store or using the registered transport. """ def test_avoid_imported_WSDL_fetching(self): # Prepare data. url_imported = "suds://wsdl_imported" wsdl_import_wrapper = wsdl_import_wrapper_format(url_imported) wsdl_imported = wsdl_imported_format() # Add to cache. cache = MockCache() store1 = MockDocumentStore(wsdl=wsdl_import_wrapper, wsdl_imported=wsdl_imported) c1 = suds.client.Client("suds://wsdl", cachingpolicy=1, cache=cache, documentStore=store1, transport=MockTransport()) assert store1.mock_log == ["suds://wsdl", "suds://wsdl_imported"] assert len(cache.mock_data) == 1 wsdl_object_id, wsdl_object = next(iteritems(cache.mock_data)) assert wsdl_object.__class__ is suds.wsdl.Definitions # Reuse from cache. cache.mock_log = [] store2 = MockDocumentStore(wsdl=wsdl_import_wrapper) c2 = suds.client.Client("suds://wsdl", cachingpolicy=1, cache=cache, documentStore=store2, transport=MockTransport()) assert cache.mock_log == [("get", [wsdl_object_id])] assert store2.mock_log == [] def test_avoid_external_XSD_fetching(self): # Prepare document content. xsd_target_namespace = "balancana" wsdl = testutils.wsdl("""\ """, xsd_target_namespace=xsd_target_namespace) external_xsd_format = """\ """ external_xsd1 = b(external_xsd_format % (1,)) external_xsd2 = b(external_xsd_format % (2,)) # Add to cache. cache = MockCache() store1 = MockDocumentStore(wsdl=wsdl, imported_xsd=external_xsd1, included_xsd=external_xsd2) c1 = suds.client.Client("suds://wsdl", cachingpolicy=1, cache=cache, documentStore=store1, transport=MockTransport()) assert store1.mock_log == ["suds://wsdl", "suds://imported_xsd", "suds://included_xsd"] assert len(cache.mock_data) == 1 wsdl_object_id, wsdl_object = next(iteritems(cache.mock_data)) assert wsdl_object.__class__ is suds.wsdl.Definitions # Reuse from cache. cache.mock_log = [] store2 = MockDocumentStore(wsdl=wsdl) c2 = suds.client.Client("suds://wsdl", cachingpolicy=1, cache=cache, documentStore=store2, transport=MockTransport()) assert cache.mock_log == [("get", [wsdl_object_id])] assert store2.mock_log == [] @pytest.mark.parametrize("importing_WSDL_cached", (False, True)) def test_importing_WSDL_from_cache_avoids_store_avoids_transport(self, importing_WSDL_cached): """ When a requested WSDL schema is located in the client's cache, it should be read from there instead of fetching its data from the client's document store or using its registered transport. When it is is not located in the cache but can be found in the client's document store, it should be fetched from there but not using the client's registered transport. Note that this test makes sense only when caching raw XML documents (cachingpolicy == 0) and not when caching final WSDL objects (cachingpolicy == 1). """ # Prepare test data. url_imported = "suds://wsdl_imported" imported_xsd_namespace = "imported WSDL's XSD namespace" wsdl_import_wrapper = wsdl_import_wrapper_format(url_imported) wsdl_imported = wsdl_imported_format( '', target_xsd_namespace=imported_xsd_namespace) wsdl_imported_element_id = ("Pistachio", imported_xsd_namespace) # Add to cache, making sure the imported WSDL schema is read from the # document store and not fetched using the client's registered # transport. cache = MockCache() store1 = MockDocumentStore(wsdl=wsdl_import_wrapper, wsdl_imported=wsdl_imported) c1 = suds.client.Client("suds://wsdl", cachingpolicy=0, cache=cache, documentStore=store1, transport=MockTransport()) assert [x for x, y in cache.mock_log] == ["get", "put"] * 2 id_wsdl = cache.mock_log[0][1][0] assert cache.mock_log[1][1][0] == id_wsdl id_wsdl_imported = cache.mock_log[2][1][0] assert cache.mock_log[3][1][0] == id_wsdl_imported assert id_wsdl_imported != id_wsdl assert store1.mock_log == ["suds://wsdl", "suds://wsdl_imported"] assert len(cache.mock_data) == 2 wsdl_imported_document = cache.mock_data[id_wsdl_imported] cached_definitions_element = wsdl_imported_document.root().children[0] cached_schema_element = cached_definitions_element.children[0] cached_external_element = cached_schema_element.children[0] schema = c1.wsdl.schema external_element = schema.elements[wsdl_imported_element_id].root assert cached_external_element is external_element # Import the WSDL schema from the cache without fetching it using the # document store or the transport. cache.mock_log = [] if importing_WSDL_cached: cache.mock_put_config = MockCache.FAIL store2 = MockDocumentStore(mock_fail=True) else: del cache.mock_data[id_wsdl] assert len(cache.mock_data) == 1 store2 = MockDocumentStore(wsdl=wsdl_import_wrapper) c2 = suds.client.Client("suds://wsdl", cachingpolicy=0, cache=cache, documentStore=store2, transport=MockTransport()) expected_cache_operations = [("get", id_wsdl)] if not importing_WSDL_cached: expected_cache_operations.append(("put", id_wsdl)) expected_cache_operations.append(("get", id_wsdl_imported)) cache_operations = [(x, y[0]) for x, y in cache.mock_log] assert cache_operations == expected_cache_operations if not importing_WSDL_cached: assert store2.mock_log == ["suds://wsdl"] assert len(cache.mock_data) == 2 assert cache.mock_data[id_wsdl_imported] is wsdl_imported_document schema = c2.wsdl.schema external_element = schema.elements[wsdl_imported_element_id].root assert cached_external_element is external_element @pytest.mark.parametrize("caching_policy", (0, 1)) def test_using_cached_WSDL_avoids_store_avoids_transport(self, caching_policy): """ When a client's WSDL schema is located in the cache, it should be read from there instead of fetching its data from the client's document store or using its registered transport. When it is is not located in the cache but can be found in the client's document store, it should be fetched from there but not using the client's registered transport. """ # Add to cache, making sure the WSDL schema is read from the document # store and not fetched using the client's registered transport. cache = MockCache() store1 = MockDocumentStore(umpala=testutils.wsdl("")) c1 = suds.client.Client("suds://umpala", cachingpolicy=caching_policy, cache=cache, documentStore=store1, transport=MockTransport()) assert [x for x, y in cache.mock_log] == ["get", "put"] id = cache.mock_log[0][1][0] assert id == cache.mock_log[1][1][0] assert len(cache.mock_data) == 1 if caching_policy == 0: # Cache contains SAX XML documents. wsdl_document = next(itervalues(cache.mock_data)) assert wsdl_document.__class__ is suds.sax.document.Document wsdl_cached_root = wsdl_document.root() else: # Cache contains complete suds WSDL objects. wsdl = next(itervalues(cache.mock_data)) assert wsdl.__class__ is suds.wsdl.Definitions wsdl_cached_root = wsdl.root assert c1.wsdl.root is wsdl_cached_root # Make certain the same WSDL schema is fetched from the cache and not # using the document store or the transport. cache.mock_log = [] cache.mock_put_config = MockCache.FAIL store2 = MockDocumentStore(mock_fail=True) c2 = suds.client.Client("suds://umpala", cachingpolicy=caching_policy, cache=cache, documentStore=store2, transport=MockTransport()) assert cache.mock_log == [("get", [id])] assert c2.wsdl.root is wsdl_cached_root @pytest.mark.parametrize("external_reference_tag", ("import", "include")) @pytest.mark.parametrize("main_WSDL_cached", (False, True)) def test_using_cached_XSD_schema_avoids_store_avoids_transport(self, external_reference_tag, main_WSDL_cached): """ When an imported or included XSD schema is located in the client's cache, it should be read from there instead of fetching its data from the client's document store or using its registered transport. When it is is not located in the cache but can be found in the client's document store, it should be fetched from there but not using the client's registered transport. Note that this test makes sense only when caching raw XML documents (cachingpolicy == 0) and not when caching final WSDL objects (cachingpolicy == 1). """ # Prepare document content. xsd_target_namespace = "my xsd namespace" wsdl = testutils.wsdl('' % ( external_reference_tag,), xsd_target_namespace=xsd_target_namespace) external_schema = b("""\ """) # Imported XSD schema items retain their namespace, while included ones # get merged into the target namespace. external_element_namespace = None if external_reference_tag == "include": external_element_namespace = xsd_target_namespace external_element_id = ("external", external_element_namespace) # Add to cache. cache = MockCache() store1 = MockDocumentStore(wsdl=wsdl, external=external_schema) c1 = suds.client.Client("suds://wsdl", cachingpolicy=0, cache=cache, documentStore=store1, transport=MockTransport()) assert [x for x, y in cache.mock_log] == ["get", "put"] * 2 id_wsdl = cache.mock_log[0][1][0] assert id_wsdl == cache.mock_log[1][1][0] id_xsd = cache.mock_log[2][1][0] assert id_xsd == cache.mock_log[3][1][0] assert len(cache.mock_data) == 2 wsdl_document = cache.mock_data[id_wsdl] assert c1.wsdl.root is wsdl_document.root() # Making sure id_xsd refers to the actual external XSD is a bit tricky # due to the fact that the WSDL object merged in the external XSD # content and lost the reference to the external XSD object itself. As # a workaround we make sure that the XSD schema XML element read from # the XSD object cached as id_xsd matches the one read from the WSDL # object's XSD schema. xsd_imported_document = cache.mock_data[id_xsd] cached_external_element = xsd_imported_document.root().children[0] external_element = c1.wsdl.schema.elements[external_element_id].root assert cached_external_element is external_element # Make certain the same external XSD document is fetched from the cache # and not using the document store or the transport. cache.mock_log = [] if main_WSDL_cached: cache.mock_put_config = MockCache.FAIL store2 = MockDocumentStore(mock_fail=True) else: del cache.mock_data[id_wsdl] assert len(cache.mock_data) == 1 store2 = MockDocumentStore(wsdl=wsdl) c2 = suds.client.Client("suds://wsdl", cachingpolicy=0, cache=cache, documentStore=store2, transport=MockTransport()) expected_cache_operations = [("get", id_wsdl)] if not main_WSDL_cached: expected_cache_operations.append(("put", id_wsdl)) expected_cache_operations.append(("get", id_xsd)) cache_operations = [(x, y[0]) for x, y in cache.mock_log] assert cache_operations == expected_cache_operations if not main_WSDL_cached: assert store2.mock_log == ["suds://wsdl"] assert len(cache.mock_data) == 2 assert cache.mock_data[id_xsd] is xsd_imported_document external_element = c2.wsdl.schema.elements[external_element_id].root assert cached_external_element is external_element class TestCacheUsage: """suds.client.Client cache component usage tests.""" @pytest.mark.parametrize("cache", ( None, suds.cache.NoCache(), suds.cache.ObjectCache())) def test_avoiding_default_cache_construction(self, cache, monkeypatch): """Explicitly specified cache avoids default cache construction.""" def construct_default_cache(*args, **kwargs): pytest.fail("Unexpected default cache instantiation.") class MockStore(suds.store.DocumentStore): def open(self, *args, **kwargs): raise MyException monkeypatch.setattr(suds.cache, "ObjectCache", construct_default_cache) monkeypatch.setattr(suds.store, "DocumentStore", MockStore) pytest.raises(MyException, suds.client.Client, "suds://some_URL", documentStore=MockStore(), cache=cache) def test_default_cache_construction(self, monkeypatch): """ Test when and how client creates its default cache object. We use a dummy store to get an expected exception rather than attempting to access the network, in case the test fails and the expected default cache object does not get created or gets created too late. """ def construct_default_cache(days): assert days == 1 raise MyException class MockStore(suds.store.DocumentStore): def open(self, *args, **kwargs): pytest.fail("Default cache not created in time.") monkeypatch.setattr(suds.cache, "ObjectCache", construct_default_cache) monkeypatch.setattr(suds.store, "DocumentStore", MockStore) pytest.raises(MyException, suds.client.Client, "suds://some_URL", documentStore=MockStore()) @pytest.mark.parametrize("cache", (object(), MyException())) def test_reject_invalid_cache_class(self, cache, monkeypatch): monkeypatch.delitem(locals(), "e", False) e = pytest.raises(AttributeError, suds.client.Client, "suds://some_URL", cache=cache).value try: expected_error = '"cache" must be: (%r,)' assert str(e) == expected_error % (suds.cache.Cache,) finally: del e # explicitly break circular reference chain in Python 3 class TestStoreUsage: """suds.client.Client document store component usage tests.""" @pytest.mark.parametrize("store", (object(), suds.cache.NoCache())) def test_reject_invalid_store_class(self, store, monkeypatch): monkeypatch.delitem(locals(), "e", False) e = pytest.raises(AttributeError, suds.client.Client, "suds://some_URL", documentStore=store, cache=None).value try: expected_error = '"documentStore" must be: (%r,)' assert str(e) == expected_error % (suds.store.DocumentStore,) finally: del e # explicitly break circular reference chain in Python 3 class TestTransportUsage: """suds.client.Client transport component usage tests.""" def test_default_transport(self): client = testutils.client_from_wsdl(testutils.wsdl("")) expected = suds.transport.https.HttpAuthenticated assert client.options.transport.__class__ is expected @pytest.mark.parametrize("exception", ( MyException(), # non-TransportError exception suds.transport.TransportError("huku", 666))) def test_error_on_open(self, monkeypatch, exception): monkeypatch.delitem(locals(), "e_info", False) transport = MockTransport(open_data=exception) e_info = pytest.raises(exception.__class__, suds.client.Client, "url", cache=None, transport=transport) try: assert e_info.value is exception finally: del e_info # explicitly break circular reference chain in Python 3 def test_error_on_send__non_transport(self): e = MyException() t = MockTransport(send_data=e) store = MockDocumentStore(wsdl=testutils.wsdl("", operation_name="g")) client = suds.client.Client("suds://wsdl", documentStore=store, cache=None, transport=t) assert pytest.raises(MyException, client.service.g).value is e #TODO: The test_error_on_send__transport() test should be made much more # detailed. suds.transport.TransportError exceptions get handled in a much # more complex way then is demonstrated here, for example: # - some HTTP status codes result in an exception being raised and some in # different handling # - an exception may be raised or returned depending on the # suds.options.faults suds option # Also, the whole concept of re-raising a TransportError exception as a # simple Exception instance seems like bad design. For now this rough test # just demonstrates that suds.transport.TransportError exceptions get # handled differently from other exception types. def test_error_on_send__transport(self, monkeypatch): monkeypatch.delitem(locals(), "e", False) t = MockTransport(send_data=suds.transport.TransportError("huku", 666)) store = MockDocumentStore(wsdl=testutils.wsdl("", operation_name="g")) client = suds.client.Client("suds://wsdl", documentStore=store, cache=None, transport=t) e = pytest.raises(Exception, client.service.g).value try: assert e.__class__ is Exception assert e.args == ((666, "huku"),) finally: del e # explicitly break circular reference chain in Python 3 def test_nosend_should_avoid_transport_sends(self): wsdl = testutils.wsdl("") t = MockTransport() client = testutils.client_from_wsdl(wsdl, nosend=True, transport=t) client.service.f() def test_operation_request_and_reply(self): xsd_content = '' web_service_URL = "Great minds think alike" xsd_target_namespace = "omicron psi" wsdl = testutils.wsdl(xsd_content, operation_name="pi", xsd_target_namespace=xsd_target_namespace, input="Data", output="Data", web_service_URL=web_service_URL) test_input_data = "Riff-raff" test_output_data = "La-di-da-da-da" store = MockDocumentStore(wsdl=wsdl) transport = MockTransport(send_data=b("""\ %s """ % (xsd_target_namespace, test_output_data))) client = suds.client.Client("suds://wsdl", documentStore=store, cache=None, transport=transport) assert transport.mock_log == [] reply = client.service.pi(test_input_data) assert len(transport.mock_log) == 1 assert transport.mock_log[0][0] == "send" assert transport.mock_log[0][1][0] == web_service_URL request_message = transport.mock_log[0][1][1] assert b(xsd_target_namespace) in request_message assert b(test_input_data) in request_message assert reply == test_output_data assert client.messages.get("rx") == client.last_received() assert client.messages.get("tx") == client.last_sent() @pytest.mark.parametrize("transport", (object(), suds.cache.NoCache())) def test_reject_invalid_transport_class(self, transport, monkeypatch): monkeypatch.delitem(locals(), "e", False) e = pytest.raises(AttributeError, suds.client.Client, "suds://some_URL", transport=transport, cache=None).value try: expected_error = '"transport" must be: (%r,)' assert str(e) == expected_error % (suds.transport.Transport,) finally: del e # explicitly break circular reference chain in Python 3 @pytest.mark.parametrize("url", test_URL_data) def test_WSDL_transport(self, url): store = MockDocumentStore() t = MockTransport(open_data=testutils.wsdl("")) suds.client.Client(url, cache=None, documentStore=store, transport=t) assert t.mock_log == [("open", [url])] def test_WSDL_transport_headers(self): url = test_URL_data[0] store = MockDocumentStore() t = MockTransport(open_data=testutils.wsdl("")) headers = {'foo': 'bar'} t.options.headers = headers # since we create a custom client, options must be set explicitly suds.client.Client(url, cache=None, documentStore=store, transport=t) assert len(t.mock_requests) == 1 assert t.mock_requests[0].headers == headers @pytest.mark.parametrize("url", test_URL_data) def test_imported_WSDL_transport(self, url): wsdl_import_wrapper = wsdl_import_wrapper_format(url) wsdl_imported = wsdl_imported_format("") store = MockDocumentStore(wsdl=wsdl_import_wrapper) t = MockTransport(open_data=wsdl_imported) suds.client.Client("suds://wsdl", cache=None, documentStore=store, transport=t) assert t.mock_log == [("open", [url])] @pytest.mark.parametrize("url", test_URL_data) @pytest.mark.parametrize("external_reference_tag", ("import", "include")) def test_external_XSD_transport(self, url, external_reference_tag): xsd_content = '' % dict( tag=external_reference_tag, url=url) store = MockDocumentStore(wsdl=testutils.wsdl(xsd_content)) t = MockTransport(open_data=b("""\ """)) suds.client.Client("suds://wsdl", cache=None, documentStore=store, transport=t) assert t.mock_log == [("open", [url])] class TestWSDLImportWithDifferentTargetNamespaces: """ Import WSDL with different target namespace variations. """ def test_imported_entity_reference_with_same_imported_and_base_ns(self): tns = "my shared WSDL schema namespace" url_imported = "suds://wsdl_imported" wsdl_import_wrapper = wsdl_import_wrapper_format(url_imported, imported_reference_ns=tns, target_namespace=tns); wsdl_imported = wsdl_imported_format(target_namespace=tns) store = MockDocumentStore(wsdl=wsdl_import_wrapper, wsdl_imported=wsdl_imported) suds.client.Client("suds://wsdl", cache=None, documentStore=store) def test_imported_entity_reference_using_base_namespace(self): """ Imported WSDL entity references using base namespace should not work. """ tns_import_wrapper = "my wrapper WSDL schema" tns_imported = "my imported WSDL schema" url_imported = "suds://wsdl_imported" wsdl_import_wrapper = wsdl_import_wrapper_format(url_imported, imported_reference_ns=tns_import_wrapper, target_namespace=tns_import_wrapper); wsdl_imported = wsdl_imported_format(target_namespace=tns_imported) store = MockDocumentStore(wsdl=wsdl_import_wrapper, wsdl_imported=wsdl_imported) e = pytest.raises(Exception, suds.client.Client, "suds://wsdl", cache=None, documentStore=store).value try: assert e.__class__ is Exception assert str(e) == "binding 'imported_reference_ns:dummy', not-found" finally: del e # explicitly break circular reference chain in Python 3 def test_imported_entity_reference_using_correct_namespace(self): """ Imported WSDL entity references using imported namespace should work. """ tns_import_wrapper = "my wrapper WSDL schema" tns_imported = "my imported WSDL schema" url_imported = "suds://wsdl_imported" wsdl_import_wrapper = wsdl_import_wrapper_format(url_imported, imported_reference_ns=tns_imported, target_namespace=tns_import_wrapper); wsdl_imported = wsdl_imported_format(target_namespace=tns_imported) store = MockDocumentStore(wsdl=wsdl_import_wrapper, wsdl_imported=wsdl_imported) suds.client.Client("suds://wsdl", cache=None, documentStore=store) #TODO: extract WSDL processing tests to a separate test module def test_resolving_references_to_later_entities_in_XML(): """ Referencing later entities in XML should be supported. When we reference another entity in our WSDL, there should be no difference whether that entity has been defined before or after the referencing entity in the underlying XML structure. """ wsdl = b("""\ """) store = MockDocumentStore(wsdl=wsdl) c = suds.client.Client("suds://wsdl", cache=None, documentStore=store) service = c.wsdl.services[0] port = service.ports[0] binding = port.binding port_type = binding.type operation = port_type.operations['f'] input_data = operation.input input_part = input_data.parts[0] input_element = input_part.element assert input_element == ('Lollypop', 'xsd-ns') def test_sortnamespaces_default(): """ Option to not sort namespaces. """ wsdl = b("""\ """) store = MockDocumentStore(wsdl=wsdl) client = suds.client.Client("suds://wsdl", cache=None, documentStore=store) prefixes = client.sd[0].prefixes assert str(prefixes[0][1]) == 'urn:wsdl' assert str(prefixes[1][1]) == 'urn:wsdl:account' assert str(prefixes[2][1]) == 'urn:wsdl:common' def test_sortnamespaces_turned_off(): """ Option to not sort namespaces. """ wsdl = b("""\ """) store = MockDocumentStore(wsdl=wsdl) client = suds.client.Client("suds://wsdl", cache=None, documentStore=store, sortNamespaces=False) prefixes = client.sd[0].prefixes assert str(prefixes[0][1]) == 'urn:wsdl:common' assert str(prefixes[1][1]) == 'urn:wsdl' assert str(prefixes[2][1]) == 'urn:wsdl:account' class TestRecursiveWSDLImport: """ Test different recursive WSDL import variations. As WSDL imports are nothing but forward declarations, and not component inclusions, recursive WSDL imports are well defined and should be supported. """ @staticmethod def __wsdl_binding(tns_binding, tns_main, url_main): return b("""\ """ % dict(tns=tns_binding, tns_imported=tns_main, url_imported=url_main)) @staticmethod def __wsdl_no_binding(tns_main, tns_binding, url_binding): return b("""\ """ % dict(tns=tns_main, tns_imported=tns_binding, url_imported=url_binding)) def test_recursive_WSDL_import_with_single_URL_per_WSDL(self): url_main = "suds://wsdl_main" tns_main = "main-wsdl" url_binding = "suds://wsdl_binding" tns_binding = "binding-wsdl" wsdl_binding = self.__wsdl_binding(tns_binding, tns_main, url_main) wsdl_main = self.__wsdl_no_binding(tns_main, tns_binding, url_binding) store = MockDocumentStore(wsdl_main=wsdl_main, wsdl_binding=wsdl_binding) suds.client.Client(url_main, cache=None, documentStore=store) def test_recursive_WSDL_import_with_multiple_URLs_per_WSDL(self): url_main1 = "suds://wsdl_main_1" url_main2 = "suds://wsdl_main_2" tns_main = "main-wsdl" url_binding = "suds://wsdl_binding" tns_binding = "binding-wsdl" wsdl_binding = self.__wsdl_binding(tns_binding, tns_main, url_main2) wsdl_main = self.__wsdl_no_binding(tns_main, tns_binding, url_binding) store = MockDocumentStore(wsdl_main_1=wsdl_main, wsdl_main_2=wsdl_main, wsdl_binding=wsdl_binding) suds.client.Client(url_main1, cache=None, documentStore=store) def test_WSDL_self_import(self): url = "suds://wsdl" wsdl = b("""\ """ % dict(url_imported=url)) store = MockDocumentStore(wsdl=wsdl) suds.client.Client(url, cache=None, documentStore=store) suds-1.1.2/tests/test_compare_sax.py000066400000000000000000000256761425611400200175600ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ CompareSAX testing utility unit tests. """ if __name__ == "__main__": import testutils testutils.run_using_pytest(globals()) import suds import suds.sax.document import suds.sax.parser from testutils.assertion import assert_no_output from testutils.compare_sax import CompareSAX import pytest from six import text_type, u import xml.sax # CompareSAX class uses Python assertions to report failed comparison results # so we need to skip the tests in this module if Python assertions have been # disabled in the CompareSAX implementation module. skip_test_if_CompareSAX_assertions_disabled = pytest.mark.skipif( not CompareSAX.assertions_enabled(), reason="CompareSAX assertions disabled") @skip_test_if_CompareSAX_assertions_disabled @pytest.mark.parametrize("data", ( "", "", '', "xml")) def test_failed_parsing(data, capsys): pytest.raises(xml.sax.SAXParseException, CompareSAX.data2data, data, data) assert_no_output(capsys) class TestMatched: """Successful CompareSAX matching tests.""" @skip_test_if_CompareSAX_assertions_disabled def test_empty_document(self, capsys): a = suds.sax.document.Document() b = suds.sax.document.Document() CompareSAX.document2document(a, b) assert_no_output(capsys) @skip_test_if_CompareSAX_assertions_disabled @pytest.mark.parametrize(("data1", "data2"), ( # Simple matches. ('', ''), ('', ''), ('', ''), # Extra namespace declarations. ('', ''), ('', ''), ('', ''), # Mismatched namespace prefixes. ('', ''), ('', ''), # Numeric unicode character references. (u("\u2606"), "&#%d;" % (0x2606,)))) def test_data2data(self, data1, data2, capsys): CompareSAX.data2data(data1, data2) assert_no_output(capsys) @skip_test_if_CompareSAX_assertions_disabled @pytest.mark.parametrize("type1", (suds.byte_str, text_type)) @pytest.mark.parametrize("type2", (suds.byte_str, text_type)) def test_string_input_types(self, type1, type2, capsys): xml = "" CompareSAX.data2data(type1(xml), type2(xml)) assert_no_output(capsys) @skip_test_if_CompareSAX_assertions_disabled def test_xml_encoding(self, capsys): """Test that the encoding listed in the XML declaration is honored.""" xml_format = u('\u00D8') data1 = (xml_format % ("UTF-8",)).encode('utf-8') data2 = (xml_format % ("latin1",)).encode('latin1') CompareSAX.data2data(data1, data2) assert_no_output(capsys) class TestMismatched: """Failed CompareSAX matching tests.""" @skip_test_if_CompareSAX_assertions_disabled @pytest.mark.parametrize(("data1", "data2", "expected_context"), ( # Different element namespaces. ("", '', "data2data..namespace"), ('', '', "data2data..namespace"), ('', '', "data2data...namespace"), ('', '', "data2data....namespace"), # Different textual content in text only nodes. ("one", "two", "data2data..text"), ("x", "x ", "data2data..text"), ("x", "x ", "data2data..text"), ("x ", "x ", "data2data..text"), (" x", "x", "data2data..text"), (" x", "x", "data2data..text"), (" x", " x", "data2data..text"), ("x", "X", "data2data....text"), ("xy", "xY", "data2data....text"), # Different textual content in mixed content nodes with children. ("4242", "42 42", "data2data..text"), # Differently named elements. ("", "", "data2data."), ("", "", "data2data.."), ("", "", "data2data.."), ("", "", "data2data.."), ("", "", "data2data..."), ("", "", "data2data..."), # Extra/missing non-root element. ("", "", "data2data."), ("", "", "data2data."), ("", "", "data2data."), ("", "", "data2data."), ("", "", "data2data."), ("", "", "data2data."), # Multiple differences. ("", "", "data2data."), ("", '', "data2data..namespace"), ("", "", "data2data.."), ("", '', "data2data...namespace"))) def test_data2data(self, data1, data2, expected_context, capsys): pytest.raises(AssertionError, CompareSAX.data2data, data1, data2) _assert_context_output(capsys, expected_context) @skip_test_if_CompareSAX_assertions_disabled def test_document2document_context(self, capsys): a = suds.sax.document.Document() b = suds.sax.parser.Parser().parse(string=suds.byte_str("")) pytest.raises(AssertionError, CompareSAX.document2document, a, b) _assert_context_output(capsys, "document2document") @skip_test_if_CompareSAX_assertions_disabled def test_document2element_context(self, capsys): a = suds.sax.parser.Parser().parse(string=suds.byte_str("1")) b = suds.sax.parser.Parser().parse(string=suds.byte_str("2")) pytest.raises(AssertionError, CompareSAX.document2element, a, b.root()) _assert_context_output(capsys, "document2element..text") @skip_test_if_CompareSAX_assertions_disabled def test_element2element_context(self, capsys): Parser = suds.sax.parser.Parser e1 = Parser().parse(string=suds.byte_str("")).root() e2 = Parser().parse(string=suds.byte_str("")).root() pytest.raises(AssertionError, CompareSAX.element2element, e1, e2) _assert_context_output(capsys, "element2element.") @skip_test_if_CompareSAX_assertions_disabled def test_element2element_context_invalid_name__left(self, capsys): Parser = suds.sax.parser.Parser e = Parser().parse(string=suds.byte_str("")).root() e_invalid = object() pytest.raises(AssertionError, CompareSAX.element2element, e_invalid, e) _assert_context_output(capsys, "element2element.") @skip_test_if_CompareSAX_assertions_disabled def test_element2element_context_invalid_name__right(self, capsys): Parser = suds.sax.parser.Parser e = Parser().parse(string=suds.byte_str("")).root() e_invalid = object() pytest.raises(AssertionError, CompareSAX.element2element, e, e_invalid) _assert_context_output(capsys, "element2element.") @skip_test_if_CompareSAX_assertions_disabled def test_empty_vs_non_empty_document(self, capsys): document = suds.sax.document.Document() data = "" pytest.raises(AssertionError, CompareSAX.document2data, document, data) _assert_context_output(capsys, "document2data") #TODO: TestSAXModelFeatures tests should be removed once their respective SAX # document model features get tested by SAX document model specific unit tests. #TODO: Additional missing suds SAX document model unit tests: # * SAX parser fails on documents with multiple root elements. # * SAX document may contain at most one element, accessible as root(). # * SAX document append() overwrites the root element silently. class TestSAXModelFeatures: """SAX document model feature testing using the CompareSAX interface.""" @skip_test_if_CompareSAX_assertions_disabled @pytest.mark.parametrize(("data1", "data2"), ( # Differently placed default namespace declaration. ('', ''), # Differently placed namespace prefix declaration. ('', ''), # Element's textual content merged. ("111222", "111222"), ("111222", "111222"), ("111222", "111222"), # Explicit "" namespace == no prefix or default namespace. ('', ""), ('', ""), # Extra leading/trailing textual whitespace trimmed in mixed content # elements with more than one child element. (" \n\n \t\t\n\n", ""), (" \nxxx\n \t\t\n\n", "xxx"))) def test_data2data(self, data1, data2, capsys): CompareSAX.data2data(data1, data2) assert_no_output(capsys) def _assert_context_output(capsys, context): """ Test utility asserting an expected captured stderr context output and no captured stdout output. """ out, err = capsys.readouterr() assert not out assert err == "Failed SAX XML comparison context:\n %s\n" % (context,) suds-1.1.2/tests/test_date_time.py000066400000000000000000000313431425611400200171760ustar00rootroot00000000000000# This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jeff Ortel ( jortel@redhat.com ) """ Date & time related suds Python library unit tests. Implemented using the 'pytest' testing framework. """ if __name__ == "__main__": import testutils testutils.run_using_pytest(globals()) from suds.sax.date import (FixedOffsetTimezone, Date, DateTime, Time, UtcTimezone) import pytest import datetime class _Dummy: """Class for testing unknown object class handling.""" pass """Invalid date strings reused for both date & datetime testing.""" _invalid_date_strings = ( "", "abla", "12", "12-01", "-12-01", "1900-01", "+1900-10-01", # Plus sign not allowed. "1900-13-01", # Invalid month. "1900-02-30", # Invalid day. "2001-02-29", # Not a leap year. "2100-02-29", # Not a leap year. " 1900-01-01", "1900- 01-01", "1900-01 -01", "1900-01-01 ", "1900-13-011", "1900-01-01X", "1900-01-01T", # 'T' is a date/time separator for DateTime. # Invalid time zone indicators. "1900-01-01 +17:00", "1900-01-01+ 17:00", "1900-01-01*17:00", "1900-01-01 17:00", "1900-01-01+17:", "1900-01-01+170", "1900-01-01+1730", "1900-01-01+170:00", "1900-01-01+17:00:00", "1900-01-01-:4", "1900-01-01-2a:00", "1900-01-01-222:00", "1900-01-01-12:000" "1900-01-01+00:60", "1900-01-01-00:99") """Invalid date strings reused for both time & datetime testing.""" _invalid_time_strings = ( "", "bunga", "12", "::", "12:", "12:01", "12:01:", "12:01: 00", "12:01: 00", "23: 01:00", " 23:01:00", "23 :01:00", "23::00", "23:000:00", "023:00:00", "23:00:000", "25:01:00", "-1:01:00", "24:01:00", "23:-1:00", "23:61:00", "23:60:00", "23:59:-1", "23:59:61", "23:59:60", "7.59.13", "7-59-13", "-0:01:00", "23:-0:00", "23:59:-0", "23:59:6.a", "23:59:6.", "23:59:6:0", "23:59:6.12x", "23:59:6.12x45", "23:59:6.999999 ", "23:59:6.999999x", "T23:59:6", # Invalid time zone indicators. "13:27:04 -10:00", "13:27:04- 10:00", "13:27:04*17:00", "13:27:04 17:00", "13:27:04-003", "13:27:04-003:00", "13:27:04+00:002", "13:27:04-13:60", "13:27:04-121", "13:27:04-1210", "13:27:04-121:00", "13:27:04+12:", "13:27:04+12:00:00", "13:27:04-:13" "13:27:04-24:00" "13:27:04+99:00") class TestDate: """Tests for the suds.sax.date.Date class.""" def testConstructFromDate(self): date = datetime.date(2001, 12, 10) assert Date(date).value is date def testConstructFromDateTime_naive(self): date = datetime.datetime(2001, 12, 10, 10, 50, 21, 32132) assert Date(date).value == datetime.date(2001, 12, 10) @pytest.mark.parametrize("hours", (5, 20)) def testConstructFromDateTime_tzAware(self, hours): tz = FixedOffsetTimezone(10) date = datetime.datetime(2001, 12, 10, hours, 50, 21, 32132, tzinfo=tz) assert Date(date).value == datetime.date(2001, 12, 10) @pytest.mark.parametrize(("string", "y", "m", "d"), ( ("1900-01-01", 1900, 1, 1), ("1900-1-1", 1900, 1, 1), ("1900-01-01z", 1900, 1, 1), ("1900-01-01Z", 1900, 1, 1), ("1900-01-01-02", 1900, 1, 1), ("1900-01-01+2", 1900, 1, 1), ("1900-01-01+02:00", 1900, 1, 1), ("1900-01-01+99:59", 1900, 1, 1), ("1900-01-01-21:13", 1900, 1, 1), ("2000-02-29", 2000, 2, 29))) # Leap year. def testConstructFromString(self, string, y, m, d): assert Date(string).value == datetime.date(y, m, d) @pytest.mark.parametrize("string", _invalid_date_strings) def testConstructFromString_failure(self, string): pytest.raises(ValueError, Date, string) @pytest.mark.parametrize("source", ( None, object(), _Dummy(), datetime.time(10, 10))) def testConstructFromUnknown(self, source): pytest.raises(ValueError, Date, source) @pytest.mark.parametrize(("input", "output"), ( ("1900-01-01", "1900-01-01"), ("2000-02-29", "2000-02-29"), ("1900-1-1", "1900-01-01"), ("1900-01-01z", "1900-01-01"), ("1900-01-01Z", "1900-01-01"), ("1900-01-01-02", "1900-01-01"), ("1900-01-01+2", "1900-01-01"), ("1900-01-01+02:00", "1900-01-01"), ("1900-01-01+99:59", "1900-01-01"), ("1900-01-01-21:13", "1900-01-01"))) def testConvertToString(self, input, output): assert str(Date(input)) == output class TestDateTime: """Tests for the suds.sax.date.DateTime class.""" def testConstructFromDateTime(self): dt = datetime.datetime(2001, 12, 10, 1, 1) assert DateTime(dt).value is dt dt.replace(tzinfo=UtcTimezone()) assert DateTime(dt).value is dt @pytest.mark.parametrize( ("string", "y", "M", "d", "h", "m", "s", "micros"), ( ("2013-11-19T14:05:23.428068", 2013, 11, 19, 14, 5, 23, 428068), ("2013-11-19 14:05:23.4280", 2013, 11, 19, 14, 5, 23, 428000))) def testConstructFromString(self, string, y, M, d, h, m, s, micros): assert DateTime(string).value == datetime.datetime(y, M, d, h, m, s, micros) @pytest.mark.parametrize("string", [x + "T00:00:00" for x in _invalid_date_strings] + ["2000-12-31T" + x for x in _invalid_time_strings] + [ # Invalid date/time separator characters. "2013-11-1914:05:23.428068", "2013-11-19X14:05:23.428068"]) def testConstructFromString_failure(self, string): pytest.raises(ValueError, DateTime, string) @pytest.mark.parametrize( ("string", "y", "M", "d", "h", "m", "s", "micros"), ( ("2000-2-28T23:59:59.9999995", 2000, 2, 29, 0, 0, 0, 0), ("2000-2-29T23:59:59.9999995", 2000, 3, 1, 0, 0, 0, 0), ("2013-12-31T23:59:59.9999994", 2013, 12, 31, 23, 59, 59, 999999), ("2013-12-31T23:59:59.99999949", 2013, 12, 31, 23, 59, 59, 999999), ("2013-12-31T23:59:59.9999995", 2014, 1, 1, 0, 0, 0, 0))) def testConstructFromString_subsecondRounding(self, string, y, M, d, h, m, s, micros): ref = datetime.datetime(y, M, d, h, m, s, micros) assert DateTime(string).value == ref @pytest.mark.parametrize( ("string", "y", "M", "d", "h", "m", "s", "micros", "tz_h", "tz_m"), ( ("2013-11-19T14:05:23.428068-3", 2013, 11, 19, 14, 5, 23, 428068, -3, 0), ("2013-11-19T14:05:23.068+03", 2013, 11, 19, 14, 5, 23, 68000, 3, 0), ("2013-11-19T14:05:23.428068-02:00", 2013, 11, 19, 14, 5, 23, 428068, -2, 0), ("2013-11-19T14:05:23.428068+02:00", 2013, 11, 19, 14, 5, 23, 428068, 2, 0), ("2013-11-19T14:05:23.428068-23:59", 2013, 11, 19, 14, 5, 23, 428068, -23, -59))) def testConstructFromString_timezone(self, string, y, M, d, h, m, s, micros, tz_h, tz_m): tzdelta = datetime.timedelta(hours=tz_h, minutes=tz_m) tzinfo = FixedOffsetTimezone(tzdelta) ref = datetime.datetime(y, M, d, h, m, s, micros, tzinfo=tzinfo) assert DateTime(string).value == ref @pytest.mark.parametrize("source", ( None, object(), _Dummy(), datetime.date(2010, 10, 27), datetime.time(10, 10))) def testConstructFromUnknown(self, source): pytest.raises(ValueError, DateTime, source) @pytest.mark.parametrize(("input", "output"), ( ("2013-11-19T14:05:23.428068", "2013-11-19T14:05:23.428068"), ("2013-11-19 14:05:23.4280", "2013-11-19T14:05:23.428000"), ("2013-12-31T23:59:59.9999995", "2014-01-01T00:00:00"), ("2013-11-19T14:05:23.428068-3", "2013-11-19T14:05:23.428068-03:00"), ("2013-11-19T14:05:23.068+03", "2013-11-19T14:05:23.068000+03:00"), ("2013-11-19T14:05:23.4-02:00", "2013-11-19T14:05:23.400000-02:00"), ("2013-11-19T14:05:23.410+02:00", "2013-11-19T14:05:23.410000+02:00"), ("2013-11-19T14:05:23.428-23:59", "2013-11-19T14:05:23.428000-23:59"))) def testConvertToString(self, input, output): assert str(DateTime(input)) == output class TestTime: """Tests for the suds.sax.date.Time class.""" def testConstructFromTime(self): time = datetime.time(1, 1) assert Time(time).value is time time.replace(tzinfo=UtcTimezone()) assert Time(time).value is time @pytest.mark.parametrize(("string", "h", "m", "s", "micros"), ( ("10:59:47", 10, 59, 47, 0), ("9:9:13", 9, 9, 13, 0), ("18:0:09.2139", 18, 0, 9, 213900), ("18:0:09.02139", 18, 0, 9, 21390), ("18:0:09.002139", 18, 0, 9, 2139), ("0:00:00.00013", 0, 0, 0, 130), ("0:00:00.000001", 0, 0, 0, 1), ("0:00:00.000000", 0, 0, 0, 0), ("23:59:6.999999", 23, 59, 6, 999999), ("1:13:50.0", 1, 13, 50, 0))) def testConstructFromString(self, string, h, m, s, micros): assert Time(string).value == datetime.time(h, m, s, micros) @pytest.mark.parametrize("string", _invalid_time_strings) def testConstructFromString_failure(self, string): pytest.raises(ValueError, Time, string) @pytest.mark.parametrize(("string", "h", "m", "s", "micros"), ( ("0:0:0.0000000", 0, 0, 0, 0), ("0:0:0.0000001", 0, 0, 0, 0), ("0:0:0.0000004", 0, 0, 0, 0), ("0:0:0.0000005", 0, 0, 0, 1), ("0:0:0.0000006", 0, 0, 0, 1), ("0:0:0.0000009", 0, 0, 0, 1), ("0:0:0.5", 0, 0, 0, 500000), ("0:0:0.5000004", 0, 0, 0, 500000), ("0:0:0.5000005", 0, 0, 0, 500001), ("0:0:0.50000050", 0, 0, 0, 500001), ("0:0:0.50000051", 0, 0, 0, 500001), ("0:0:0.50000055", 0, 0, 0, 500001), ("0:0:0.50000059", 0, 0, 0, 500001), ("0:0:0.5000006", 0, 0, 0, 500001), ("0:0:0.9999990", 0, 0, 0, 999999), ("0:0:0.9999991", 0, 0, 0, 999999), ("0:0:0.9999994", 0, 0, 0, 999999), ("0:0:0.99999949", 0, 0, 0, 999999), ("0:0:0.9999995", 0, 0, 1, 0), ("0:0:0.9999996", 0, 0, 1, 0), ("0:0:0.9999999", 0, 0, 1, 0))) def testConstructFromString_subsecondRounding(self, string, h, m, s, micros): assert Time(string).value == datetime.time(h, m, s, micros) @pytest.mark.parametrize( ("string", "h", "m", "s", "micros", "tz_h", "tz_m"), ( ("18:0:09.2139z", 18, 0, 9, 213900, 0, 0), ("18:0:09.2139Z", 18, 0, 9, 213900, 0, 0), ("18:0:09.2139+3", 18, 0, 9, 213900, 3, 0), ("18:0:09.2139-3", 18, 0, 9, 213900, -3, 0), ("18:0:09.2139-03", 18, 0, 9, 213900, -3, 0), ("18:0:09.2139+9:3", 18, 0, 9, 213900, 9, 3), ("18:0:09.2139+10:31", 18, 0, 9, 213900, 10, 31), ("18:0:09.2139-10:31", 18, 0, 9, 213900, -10, -31))) def testConstructFromString_timezone(self, string, h, m, s, micros, tz_h, tz_m): tzdelta = datetime.timedelta(hours=tz_h, minutes=tz_m) tzinfo = FixedOffsetTimezone(tzdelta) ref = datetime.time(h, m, s, micros, tzinfo=tzinfo) assert Time(string).value == ref @pytest.mark.parametrize("source", ( None, object(), _Dummy(), datetime.date(2010, 10, 27), datetime.datetime(2010, 10, 27, 10, 10))) def testConstructFromUnknown(self, source): pytest.raises(ValueError, Time, source) @pytest.mark.parametrize(("input", "output"), ( ("14:05:23.428068", "14:05:23.428068"), ("14:05:23.4280", "14:05:23.428000"), ("23:59:59.9999995", "00:00:00"), ("14:05:23.428068-3", "14:05:23.428068-03:00"), ("14:05:23.068+03", "14:05:23.068000+03:00"), ("14:05:23.4-02:00", "14:05:23.400000-02:00"), ("14:05:23.410+02:00", "14:05:23.410000+02:00"), ("14:05:23.428-23:59", "14:05:23.428000-23:59"))) def testConvertToString(self, input, output): assert str(Time(input)) == output suds-1.1.2/tests/test_dependency_sort.py000066400000000000000000000215201425611400200204240ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Dependency sort unit tests. Implemented using the 'pytest' testing framework. """ import testutils if __name__ == "__main__": testutils.run_using_pytest(globals()) from suds.xsd.depsort import dependency_sort import pytest from six import iteritems import copy # some of the tests in this module make sense only with assertions enabled # (note though that pytest's assertion rewriting technique, as used by default # in recent pytest releases, will keep assertions enabled inside the used test # modules even when the underlying Python interpreter has been run using the -O # command-line option) try: assert False except AssertionError: assertions_enabled = True else: assertions_enabled = False # shared test data # f --+-----+-----+ # | | | | # | v v v # | e --> d --> c --> b # | | | | # +---+-----------+-----+--> a --> x _test_dependency_tree = { "x": (), "a": ("x",), "b": ("a",), "c": ("a", "b"), "d": ("c",), "e": ("d", "a"), "f": ("e", "c", "d", "a")} def test_dependency_sort(): dependency_tree = _test_dependency_tree result = dependency_sort(dependency_tree) assert sorted(result) == sorted(iteritems(dependency_tree)) _assert_dependency_order((x[0] for x in result), dependency_tree) def test_dependency_sort_does_not_mutate_input(): dependency_tree = _test_dependency_tree # save the original dependency tree structure information expected_deps = {} expected_deps_ids = {} for x, y in iteritems(dependency_tree): expected_deps[x] = copy.copy(y) expected_deps_ids[id(x)] = id(y) # run the dependency sort dependency_sort(dependency_tree) # verify that the dependency tree structure is unchanged assert len(dependency_tree) == len(expected_deps) for key, deps in iteritems(dependency_tree): # same deps for each key assert id(deps) == expected_deps_ids[id(key)] # deps structure compare with the original copy assert deps == expected_deps[key] # explicit deps content id matching just in case the container's __eq__ # is not precise enough _assert_same_content_set(deps, expected_deps[key]) ############################################################################### # # Test utilities. # ############################################################################### def _assert_dependency_order(sequence, dependencies): """ Assert that a sequence is ordered dependencies first. The only way an earlier entry is allowed to have a later entry as its dependency is if they are both part of the same dependency cycle. """ sequence = list(sequence) dependency_closure = _transitive_dependency_closure(dependencies) for i, a in enumerate(sequence): for b in sequence[i + 1:]: a_dependent_on_b = b in dependency_closure[a] b_dependent_on_a = a in dependency_closure[b] assert b_dependent_on_a or not a_dependent_on_b def _assert_same_content_set(lhs, rhs): """Assert that two iterables have the same content (order independent).""" counter_lhs = _counter(lhs) counter_rhs = _counter(rhs) assert counter_lhs == counter_rhs def _counter(iterable): """Return an {id: count} dictionary for all items from `iterable`.""" counter = {} for x in iterable: counter[id(x)] = counter.setdefault(id(x), 0) + 1 return counter def _transitive_dependency_closure(dependencies): """ Returns a transitive dependency closure. If target A is dependent on target B, and target B is in turn dependent on target C, then target A is also implicitly dependent on target C. A transitive dependency closure is an expanded dependency collection so that in it all such implicit dependencies have been explicitly specified. """ def clone(deps): return dict((k, set(v)) for k, v in iteritems(deps)) closure = None new = clone(dependencies) while new != closure: closure = clone(new) for k, deps in iteritems(closure): for dep in deps: new[k] |= closure[dep] return closure ############################################################################### # # Test utility tests. # ############################################################################### @pytest.mark.skipif(not assertions_enabled, reason="assertions disabled") @pytest.mark.parametrize("sequence, dependencies", ( (["x", "y"], {"x": ("y",), "y": ()}), (["x", "y"], {"x": ("z",), "z": ("y",), "y": ()}), (["y", "x", "z"], {"x": ("z",), "z": ("y",), "y": ()}), (["z", "y", "x"], {"x": ("z",), "z": ("y",), "y": ()}), # unrelated element groups (["y", "a", "x", "b"], {"x": (), "y": ("x",), "a": (), "b": ("a",)}), (["x", "b", "y", "a"], {"x": (), "y": ("x",), "a": (), "b": ("a",)}), (["b", "x", "y", "a"], {"x": (), "y": ("x",), "a": (), "b": ("a",)}), (["a", "y", "b", "x"], {"x": (), "y": ("x",), "a": (), "b": ("a",)}), (["a", "b", "y", "x"], {"x": (), "y": ("x",), "a": (), "b": ("a",)}))) def test_assert_dependency_order__invalid(sequence, dependencies): pytest.raises(AssertionError, _assert_dependency_order, sequence, dependencies) @pytest.mark.parametrize("sequence, dependencies", ( (["y", "x"], {"x": ("y",), "y": ()}), (["y", "x"], {"x": ("z",), "z": ("y",), "y": ()}), (["y", "z", "x"], {"x": ("z",), "z": ("y",), "y": ()}), # unrelated element groups (["x", "y", "a", "b"], {"x": (), "y": ("x",), "a": (), "b": ("a",)}), (["x", "a", "y", "b"], {"x": (), "y": ("x",), "a": (), "b": ("a",)}), (["a", "x", "y", "b"], {"x": (), "y": ("x",), "a": (), "b": ("a",)}), (["a", "x", "b", "y"], {"x": (), "y": ("x",), "a": (), "b": ("a",)}), (["a", "b", "x", "y"], {"x": (), "y": ("x",), "a": (), "b": ("a",)}), # dependency cycle (["x", "y"], {"x": ("y",), "y": ("x",)}), (["y", "x"], {"x": ("y",), "y": ("x",)}), # elements not mentioned in the dependency tree ([1], {}), (["a"], {"x": ("y",), "y": ()}))) def test_assert_dependency_order__valid(sequence, dependencies): _assert_dependency_order(sequence, dependencies) @pytest.mark.skipif(not assertions_enabled, reason="assertions disabled") @pytest.mark.parametrize("lhs, rhs", ( # empty # ([1, 2.0, 6], [1, 2, 6]), ((), (1,)), ([2], []), ([], (4, 2)), ([], (x for x in [8, 4])), ((x for x in [1, 1]), []), # without duplicates ([1, 2, 3], [1, 2, 4]), ([1, 2, 3], [1, 2]), ([1, 2, 3], [1, 4]), ([0], [0.0]), ([0], [0.0]), # with duplicates ([1, 1], [1]), ((x for x in [1, 1]), [1]), ([1, 1], [1, 2, 1]), ([1, 1, 2, 2], [1, 2, 1]), # different object ids ([object()], [object()]))) def test_assert_same_content_set__invalid(lhs, rhs): pytest.raises(AssertionError, _assert_same_content_set, lhs, rhs) @pytest.mark.parametrize("lhs, rhs", ( # empty ((), ()), ([], []), ([], ()), ([], (x for x in [])), ((x for x in []), []), # matching without duplicates ([1, 2, 6], [1, 2, 6]), ([1, 2, 6], [6, 2, 1]), # matching with duplicates ([1, 2, 2, 6], [6, 2, 1, 2]), # matching object ids ([_assert_same_content_set], [_assert_same_content_set]))) def test_assert_same_content_set__valid(lhs, rhs): _assert_same_content_set(lhs, rhs) def test_counter(): a = object() b = object() c = object() d = object() input = [a, b, b, c, c, d, a, a, a, d, b, b, b, b, b, a, d] result = _counter(input) assert len(result) == 4 assert result[id(a)] == input.count(a) assert result[id(b)] == input.count(b) assert result[id(c)] == input.count(c) assert result[id(d)] == input.count(d) def test_counter__empty(): assert _counter([]) == {} suds-1.1.2/tests/test_document_store.py000066400000000000000000000116741425611400200203020ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Suds Python library DocumentStore unit tests. Implemented using the 'pytest' testing framework. """ if __name__ == "__main__": import testutils testutils.run_using_pytest(globals()) import suds import suds.store import pytest def test_accessing_DocumentStore_content(): content1 = suds.byte_str("one") content2 = suds.byte_str("two") content1_1 = suds.byte_str("one one") store = suds.store.DocumentStore({"1": content1}) assert len(store) == 2 _test_default_DocumentStore_content(store) _test_open(store, "1", content1) store = suds.store.DocumentStore({"1": content1, "2": content2}) assert len(store) == 3 _test_default_DocumentStore_content(store) _test_open(store, "1", content1) _test_open(store, "2", content2) store = suds.store.DocumentStore(uno=content1, due=content2) assert len(store) == 3 _test_default_DocumentStore_content(store) _test_open(store, "uno", content1) _test_open(store, "due", content2) store = suds.store.DocumentStore({"1 1": content1_1}) assert len(store) == 2 _test_default_DocumentStore_content(store) _test_open(store, "1 1", content1_1) store = suds.store.DocumentStore({"1": content1, "1 1": content1_1}) assert len(store) == 3 _test_default_DocumentStore_content(store) _test_open(store, "1", content1) _test_open(store, "1 1", content1_1) def test_accessing_missing_DocumentStore_content(): store = suds.store.DocumentStore() assert store.open("missing-content") is None assert store.open("buga-wuga://missing-content") is None assert store.open("ftp://missing-content") is None assert store.open("http://missing-content") is None assert store.open("https://missing-content") is None pytest.raises(Exception, store.open, "suds://missing-content") def test_default_DocumentStore_instance(): assert len(suds.store.defaultDocumentStore) == 1 _test_default_DocumentStore_content(suds.store.defaultDocumentStore) def test_empty_DocumentStore_instance_is_not_shared(): assert suds.store.DocumentStore() is not suds.store.defaultDocumentStore assert suds.store.DocumentStore() is not suds.store.DocumentStore() def test_updating_DocumentStore_content(): content1 = suds.byte_str("one") content2 = suds.byte_str("two") content1_1 = suds.byte_str("one one") store = suds.store.DocumentStore() assert len(store) == 1 _test_default_DocumentStore_content(store) store.update({"1": content1}) assert len(store) == 2 _test_default_DocumentStore_content(store) _test_open(store, "1", content1) store.update({"1": content1, "2": content2, "1 1": content1_1}) assert len(store) == 4 _test_default_DocumentStore_content(store) _test_open(store, "1", content1) _test_open(store, "2", content2) _test_open(store, "1 1", content1_1) store.update({"2": content2, "1 1": content1_1}) assert len(store) == 4 _test_default_DocumentStore_content(store) _test_open(store, "1", content1) _test_open(store, "2", content2) _test_open(store, "1 1", content1_1) store.update(uno=content1, due=content2) assert len(store) == 6 _test_default_DocumentStore_content(store) _test_open(store, "1", content1) _test_open(store, "2", content2) _test_open(store, "1 1", content1_1) _test_open(store, "uno", content1) _test_open(store, "due", content2) def _test_default_DocumentStore_content(store): _test_open(store, "schemas.xmlsoap.org/soap/encoding/", suds.store.soap5_encoding_schema) def _test_open(store, location, expected_content): assert store.open(location) is expected_content assert store.open("buga-wuga://%s" % location) is expected_content assert store.open("ftp://%s" % location) is expected_content assert store.open("http://%s" % location) is expected_content assert store.open("https://%s" % location) is expected_content assert store.open("suds://%s" % location) is expected_content suds-1.1.2/tests/test_input_parameters.py000066400000000000000000000621551425611400200206320ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Suds Python library web service operation input parameter related unit tests. Suds provides the user with an option to automatically 'hide' wrapper elements around simple types and allow the user to specify such parameters without explicitly creating those wrappers. For example: operation taking a parameter of type X, where X is a sequence containing only a single simple data type (e.g. string or integer) will be callable by directly passing it that internal simple data type value instead of first wrapping that value in an object of type X and then passing that wrapper object instead. Unit tests in this module make sure suds recognizes an operation's input parameters in different scenarios as expected. It does not deal with binding given argument values to an operation's input parameters or constructing an actual binding specific web service operation invocation request, although they may use such functionality as tools indicating that suds recognized an operation's input parameters correctly. """ import testutils if __name__ == "__main__": testutils.run_using_pytest(globals()) import suds import pytest class Element: """Represents elements in our XSD map test data.""" def __init__(self, name): self.name = name class XSDType: """Unwrapped parameter XSD type test data.""" def __init__(self, xsd, xsd_map): self.xsd = xsd self.xsd_map = xsd_map # Test data shared between different tests in this module. choice_choice = XSDType("""\ """, [ "complex_type", [ "sequence", [ "choice_1", [ Element("aString1"), Element("anInteger1")], "choice_2", [ Element("aString2"), Element("anInteger2")]]]]) choice_element_choice = XSDType("""\ """, [ "complex_type", [ "sequence", [ "choice_1", [ Element("aString1"), Element("anInteger1")], Element("separator"), "choice_2", [ Element("aString2"), Element("anInteger2")]]]]) choice_simple_nonoptional = XSDType("""\ """, [ "complex_type", [ "choice", [ Element("aString"), Element("anInteger")]]]) choice_with_element_and_two_element_sequence = XSDType("""\ """, [ "complex_type", [ "choice", [ Element("a"), "sequence", [ Element("b1"), Element("b2")]]]]) empty_sequence = XSDType("""\ """, [ "complex_type", [ "sequence"]]) sequence_choice_with_element_and_two_element_sequence = XSDType("""\ """, [ "complex_type", [ "sequence_1", [ "choice", [ Element("a"), "sequence_2", [ Element("b1"), Element("b2")]]]]]) sequence_with_five_elements = XSDType("""\ """, [ "complex_type", [ "sequence", [ Element("p1"), Element("p2"), Element("p3"), Element("p4"), Element("p5")]]]) sequence_with_one_element = XSDType("""\ """, [ "complex_type", [ "sequence", [ Element("param")]]]) sequence_with_two_elements = XSDType("""\ """, [ "complex_type", [ "sequence", [ Element("aString"), Element("anInteger")]]]) class TestUnsupportedParameterDefinitions: """ Tests performed on WSDL schema's containing input parameter type definitions that can not be modeled using the currently implemented suds library input parameter definition structure. The tests included in this group, most of which are expected to fail, should serve as an illustration of what type of input parameter definitions still need to be better modeled. Once this has been done, they should be refactored into separate argument parsing, input parameter definition structure and binding specific request construction tests. """ def expect_error(self, expected_error_text, *args, **kwargs): """ Assert a test function call raises an expected TypeError exception. Caught exception is considered expected if its string representation matches the given expected error text. Expected error text may be given directly or as a list/tuple containing valid alternatives. Web service operation 'f' invoker is used as the default test function. An alternate test function may be specified using the 'test_function' keyword argument. """ try: test_function = kwargs.pop("test_function") except KeyError: test_function = self.service.f e = pytest.raises(TypeError, test_function, *args, **kwargs).value try: if expected_error_text.__class__ in (list, tuple): assert str(e) in expected_error_text else: assert str(e) == expected_error_text finally: del e # explicitly break circular reference chain in Python 3 def init_function_params(self, params, **kwargs): """ Initialize a test in this group with the given parameter definition. Constructs a complete WSDL schema based on the given function parameter definition (defines a single web service operation named 'f' by default), and creates a suds Client object to be used for testing suds's web service operation invocation. An alternate operation name may be given using the 'operation_name' keyword argument. May only be invoked once per test. """ input = '%s' % (params,) assert not hasattr(self, "service") wsdl = testutils.wsdl(input, input="Wrapper", **kwargs) client = testutils.client_from_wsdl(wsdl, nosend=True) self.service = client.service @pytest.mark.parametrize("test_args_required", ( pytest.param(True, marks=pytest.mark.xfail( reason="empty choice member items not supported")), False)) def test_choice_containing_an_empty_sequence(self, test_args_required): """ Test reporting extra input parameters passed to a function taking a choice parameter group containing an empty sequence subgroup. """ self.init_function_params("""\ """) expected = "f() takes 0 to 1 positional arguments but 3 were given" if not test_args_required: expected = [expected, "f() takes 1 positional argument but 3 were given"] self.expect_error(expected, 1, None, None) @pytest.mark.parametrize("choice", ( # Explicitly marked as optional and containing only non-optional # elements. pytest.param( """\ """, marks=pytest.mark.xfail( reason="suds does not yet support minOccurs/" "maxOccurs attributes on all/choice/sequence order indicators")), # Explicitly marked as optional and containing at least one # non-optional element. """\ """, """\ """, """\ """)) def test_choice_explicitly_marked_as_optional(self, choice): """ Test reporting extra input parameters passed to a function taking a single optional choice parameter group. """ self.init_function_params(choice) expected = "f() takes 0 to 2 positional arguments but 3 were given" self.expect_error(expected, "one", None, 3) @pytest.mark.parametrize("part_name", ("uno", "due", "quatro")) def test_builtin_typed_element_parameter(part_name): """ Test correctly recognizing web service operation input structure defined by a built-in typed element. """ wsdl = suds.byte_str("""\ """ % (part_name,)) client = testutils.client_from_wsdl(wsdl, nosend=True) # Collect references to required WSDL model content. method = client.wsdl.services[0].ports[0].methods["f"] assert not method.soap.input.body.wrapped binding = method.binding.input assert binding.__class__ is suds.bindings.document.Document my_element = client.wsdl.schema.elements["MyElement", "my-namespace"] param_defs = binding.param_defs(method) _expect_params(param_defs, [("MyElement", my_element)]) @pytest.mark.parametrize("part_name", ("parameters", "pipi")) def test_explicitly_wrapped_parameter(part_name): """ Test correctly recognizing explicitly wrapped web service operation input structure which would otherwise be automatically unwrapped. """ input_schema = sequence_choice_with_element_and_two_element_sequence.xsd wsdl = _unwrappable_wsdl(part_name, input_schema) client = testutils.client_from_wsdl(wsdl, nosend=True, unwrap=False) # Collect references to required WSDL model content. method = client.wsdl.services[0].ports[0].methods["f"] assert not method.soap.input.body.wrapped binding = method.binding.input assert binding.__class__ is suds.bindings.document.Document wrapper = client.wsdl.schema.elements["Wrapper", "my-namespace"] param_defs = binding.param_defs(method) _expect_params(param_defs, [("Wrapper", wrapper)]) @pytest.mark.parametrize("param_names", ( [], ["parameters"], ["pipi"], ["fifi", "la", "fuff"])) def test_typed_parameters(param_names): """ Test correctly recognizing web service operation input structure defined with 0 or more typed input message part parameters. """ wsdl = ["""\ """] for x in param_names: part_def = '\n ' % (x,) wsdl.append(part_def) wsdl.append(""" """) wsdl = suds.byte_str("".join(wsdl)) client = testutils.client_from_wsdl(wsdl, nosend=True) # Collect references to required WSDL model content. method = client.wsdl.services[0].ports[0].methods["f"] assert not method.soap.input.body.wrapped binding = method.binding.input assert binding.__class__ is suds.bindings.document.Document my_type = client.wsdl.schema.types["MyType", "my-namespace"] # Construct expected parameter definitions. expected_param_defs = [ (param_name, [suds.bindings.binding.PartElement, param_name, my_type]) for param_name in param_names] param_defs = binding.param_defs(method) _expect_params(param_defs, expected_param_defs) @pytest.mark.parametrize("xsd_type", ( choice_choice, choice_element_choice, choice_simple_nonoptional, choice_with_element_and_two_element_sequence, empty_sequence, sequence_choice_with_element_and_two_element_sequence, sequence_with_five_elements, sequence_with_one_element, sequence_with_two_elements)) def test_unwrapped_parameter(xsd_type): """Test recognizing unwrapped web service operation input structures.""" input_schema = sequence_choice_with_element_and_two_element_sequence.xsd wsdl = _unwrappable_wsdl("part_name", input_schema) client = testutils.client_from_wsdl(wsdl, nosend=True) # Collect references to required WSDL model content. method = client.wsdl.services[0].ports[0].methods["f"] assert method.soap.input.body.wrapped binding = method.binding.input assert binding.__class__ is suds.bindings.document.Document wrapper = client.wsdl.schema.elements["Wrapper", "my-namespace"] # Construct expected parameter definitions. xsd_map = sequence_choice_with_element_and_two_element_sequence.xsd_map expected_param_defs = _parse_schema_model(wrapper, xsd_map) param_defs = binding.param_defs(method) _expect_params(param_defs, expected_param_defs) @pytest.mark.parametrize("part_name", ("parameters", "pipi")) def test_unwrapped_parameter_part_name(part_name): """ Unwrapped parameter's part name should not affect its parameter definition. """ input_schema = sequence_choice_with_element_and_two_element_sequence.xsd wsdl = _unwrappable_wsdl(part_name, input_schema) client = testutils.client_from_wsdl(wsdl, nosend=True) # Collect references to required WSDL model content. method = client.wsdl.services[0].ports[0].methods["f"] assert method.soap.input.body.wrapped binding = method.binding.input assert binding.__class__ is suds.bindings.document.Document wrapper = client.wsdl.schema.elements["Wrapper", "my-namespace"] # Construct expected parameter definitions. xsd_map = sequence_choice_with_element_and_two_element_sequence.xsd_map expected_param_defs = _parse_schema_model(wrapper, xsd_map) param_defs = binding.param_defs(method) _expect_params(param_defs, expected_param_defs) def _expect_params(param_defs, expected_param_defs): """ Assert the given parameter definition content. Given expected parameter definition content may contain the expected parameter type instance or it may contain a list/tuple describing the type instead. Type description list/tuple is expected to contain the following: 1. type object's class reference 2. type object's 'name' attribute value. 3. type object's resolved type instance reference """ assert param_defs.__class__ is list assert len(param_defs) == len(expected_param_defs) for pdef, expected_pdef in zip(param_defs, expected_param_defs): assert len(expected_pdef) in (2, 3), "bad test data" assert pdef[0] == expected_pdef[0] # name if expected_pdef[1].__class__ in (list, tuple): # type - class/name/type instance assert pdef[1].__class__ is expected_pdef[1][0] assert pdef[1].name == expected_pdef[1][1] assert pdef[1].resolve() is expected_pdef[1][2] else: assert pdef[1] is expected_pdef[1] # type - exact instance assert pdef[2:] == expected_pdef[2:] # ancestry - optional def _parse_schema_model(root, schema_model_map): """ Utility function for preparing the expected parameter definition structure based on an unwrapped input parameter's XSD type schema. Parses the XSD schema definition under a given XSD schema item and returns the expected parameter definition structure based on the given schema map. The schema map describes the expected hierarchy of items in the given XSD schema. Even though this information could be deduced from the XSD schema itself, that would require a much more complex implementation and this is supposed to be a simple testing utility. """ schema_items = {} param_defs = [] _parse_schema_model_r(schema_items, param_defs, [], root, schema_model_map) return param_defs def _parse_schema_model_r(schema_items, param_defs, ancestry, parent, schema_model_map): """Recursive implementation detail for _parse_schema_model().""" prev = None ancestry = list(ancestry) ancestry.append(parent) n = 0 for x in schema_model_map: if x.__class__ in (list, tuple): assert prev is not None, "bad schema model map" _parse_schema_model_r(schema_items, param_defs, ancestry, prev, x) continue item = parent.rawchildren[n] if isinstance(x, Element): x = x.name prev = None param_defs.append((x, item, ancestry)) else: assert isinstance(x, str), "bad schema model map" prev = item assert x not in schema_items, "duplicate schema map item names" schema_items[x] = item n += 1 assert len(parent.rawchildren) == n def _unwrappable_wsdl(part_name, param_schema): """ Return a WSDL schema byte string. The returned WSDL schema defines a single service definition with a single port containing a single function named 'f' taking automatically unwrappable input parameter using document/literal binding. The input parameter is defined as a single named input message part (name given via the 'part_name' argument) referencing an XSD schema element named 'Wrapper' located in the 'my-namespace' namespace. The wrapper element's type definition (XSD schema string) is given via the 'param_schema' argument. """ return suds.byte_str("""\ %(param_schema)s """ % {"param_schema":param_schema, "part_name":part_name}) suds-1.1.2/tests/test_mx.py000066400000000000000000000117001425611400200156620ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Suds MX system unit tests. Implemented using the 'pytest' testing framework. """ if __name__ == "__main__": import testutils testutils.run_using_pytest(globals()) from suds.mx.typer import Typer import pytest def _prefix(i): """Prefixes expected to be constructed by Typer.getprefix().""" return "ns%d" % (i,) class TestTyper: class Test_genprefix: class MockNode: def __init__(self, namespace_mapping): self.mock_call_params = [] self.__namespace_mapping = namespace_mapping def resolvePrefix(self, prefix, default): self.mock_call_params.append((prefix, default)) return self.__namespace_mapping.get(prefix, default) def test_no_mapped_prefixes(self): node = self.__class__.MockNode({}) test_namespace = "test namespace" result = Typer.genprefix(node, ("unused-prefix", test_namespace)) assert result == (_prefix(1), test_namespace) assert node.mock_call_params == [(_prefix(1), None)] def test_several_already_mapped_prefixes(self): test_namespace = "test namespace" node = self.__class__.MockNode({ _prefix(1): "another namespace", _prefix(2): "another namespace"}) result = Typer.genprefix(node, ("unused-prefix", test_namespace)) assert result == (_prefix(3), test_namespace) assert node.mock_call_params == [ (_prefix(i), None) for i in [1, 2, 3]] def test_last_free_namespace(self): test_namespace = "test namespace" node = self.__class__.MockNode(dict( (_prefix(i), "another namespace") for i in range(1, 1023))) result = Typer.genprefix(node, ("unused-prefix", test_namespace)) assert result == (_prefix(1023), test_namespace) expected_calls = [(_prefix(i), None) for i in range(1, 1024)] assert node.mock_call_params == expected_calls def test_no_free_namespace(self): test_namespace = "test namespace" node = self.__class__.MockNode(dict( (_prefix(i), "another namespace") for i in range(1, 1024))) e = pytest.raises(Exception, Typer.genprefix, node, ("unused-prefix", test_namespace)).value try: assert str(e) == "auto prefix, exhausted" finally: del e # explicitly break circular reference chain in Python 3 expected_calls = [(_prefix(i), None) for i in range(1, 1024)] assert node.mock_call_params == expected_calls def test_already_mapped_namespace_with_no_unused_prefix_before(self): test_prefix_index = 2 test_namespace = "test namespace" node = self.__class__.MockNode({ _prefix(1): "another namespace", _prefix(test_prefix_index): test_namespace, _prefix(3): "another namespace"}) result = Typer.genprefix(node, ("unused-prefix", test_namespace)) assert result == (_prefix(test_prefix_index), test_namespace) expected_calls = [(_prefix(i), None) for i in range(1, test_prefix_index + 1)] assert node.mock_call_params == expected_calls def test_already_mapped_namespace_with_unused_prefix_before(self): unused_prefix_index = 2 test_namespace = "test namespace" node = self.__class__.MockNode({ _prefix(1): "another namespace", _prefix(3): test_namespace, _prefix(4): "another namespace"}) result = Typer.genprefix(node, ("unused-prefix", test_namespace)) assert result == (_prefix(unused_prefix_index), test_namespace) expected_calls = [(_prefix(i), None) for i in range(1, unused_prefix_index + 1)] assert node.mock_call_params == expected_calls suds-1.1.2/tests/test_plugin.py000066400000000000000000000101151425611400200165330ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Suds Python library plugin unit tests. Implemented using the 'pytest' testing framework. """ import testutils if __name__ == "__main__": testutils.run_using_pytest(globals()) import pytest from suds.plugin import (DocumentContext, DocumentPlugin, InitContext, InitPlugin, MessageContext, MessagePlugin, Plugin, PluginContainer, PluginDomain) class MyDocumentPlugin(DocumentPlugin): """Local 'document' plugin class used in the tests in this module.""" pass class MyInitPlugin(InitPlugin): """Local 'init' plugin class used in the tests in this module.""" pass class MyMessagePlugin(MessagePlugin): """Local 'message' plugin class used in the tests in this module.""" pass @pytest.mark.parametrize("domain, expected_context", ( ("document", DocumentContext), ("init", InitContext), ("message", MessageContext))) def test_collecting_plugins_and_context_per_empty_domain(domain, expected_context): container = PluginContainer([]) result = getattr(container, domain) assert result.__class__ is PluginDomain assert result.ctx is expected_context assert result.plugins == [] @pytest.mark.parametrize("domain, plugin_class", ( ("document", DocumentPlugin), ("init", InitPlugin), ("message", MessagePlugin))) def test_collecting_plugins_per_domain(domain, plugin_class): plugins = [ MyDocumentPlugin(), MyDocumentPlugin(), MyMessagePlugin(), MyDocumentPlugin(), MyInitPlugin(), MyInitPlugin(), MyMessagePlugin(), InitPlugin(), MyMessagePlugin(), MyMessagePlugin(), None, MessagePlugin(), DocumentPlugin(), MyMessagePlugin(), MyDocumentPlugin(), InitPlugin(), InitPlugin(), MyInitPlugin(), MyInitPlugin(), None, MyDocumentPlugin(), DocumentPlugin(), MessagePlugin(), DocumentPlugin(), MessagePlugin(), DocumentPlugin(), InitPlugin(), MessagePlugin(), object(), DocumentPlugin(), MessagePlugin(), object(), InitPlugin(), Plugin(), Plugin(), MyInitPlugin()] container = PluginContainer(plugins) expected_plugins = [p for p in plugins if isinstance(p, plugin_class)] result = getattr(container, domain).plugins assert result == expected_plugins def test_exception_passing(): class FailingPluginException(Exception): pass class FailingPlugin(MessagePlugin): def marshalled(self, context): raise FailingPluginException container = PluginContainer([FailingPlugin()]) pytest.raises(FailingPluginException, container.message.marshalled) def test_invalid_plugin_domain(): container = PluginContainer([]) domain = "invalid_domain_name" e = pytest.raises(Exception, getattr, container, domain) try: e = e.value assert e.__class__ is Exception assert str(e) == "plugin domain (%s), invalid" % (domain,) finally: del e # explicitly break circular reference chain in Python 3 suds-1.1.2/tests/test_reader.py000066400000000000000000000075071425611400200165120ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ suds.reader module unit tests. Implemented using the 'pytest' testing framework. """ import testutils if __name__ == "__main__": testutils.run_using_pytest(globals()) import suds import suds.options import suds.reader class TestCacheItemNameMangling: """Tests suds.reader.Reader classes' cache item name mangling.""" def test_different(self): test_item_name1 = "oh my god" test_item_name2 = "ha ha ha" test_item_suffix = "that's some funky sh*t" reader = suds.reader.Reader(suds.options.Options()) mangled1 = reader.mangle(test_item_name1, test_item_suffix) mangled2 = reader.mangle(test_item_name2, test_item_suffix) assert mangled1 != mangled2 def test_inter_processes_persistence(self, tmpdir): """ Same cache item names must be mangled the same in different processes. This is a regression test against using a built-in Python hash() function internally since that function may be seeded by a process specific random seed. This Python interpreter behaviour has been enabled by default since Python 3.3 and may be explicitly enabled on earlier Python interpreter versions as well. """ test_item_name = "test string" test_item_suffix = "test suffix" reader = suds.reader.Reader(suds.options.Options()) expected = reader.mangle(test_item_name, test_item_suffix) test_file = tmpdir.join("test_mangle.py") test_file.write(""" import suds.options import suds.reader reader = suds.reader.Reader(suds.options.Options()) mangled = reader.mangle("%(test_item_name)s", "%(test_item_suffix)s") assert mangled == '%(expected)s' """ % {"expected": expected, "test_item_name": test_item_name, "test_item_suffix": test_item_suffix}) testutils.run_test_process(test_file) def test_repeatable__different_readers(self): test_item_name = "R2D2" test_item_suffix = "C3P0" reader1 = suds.reader.Reader(suds.options.Options()) reader2 = suds.reader.Reader(suds.options.Options()) mangled1 = reader1.mangle(test_item_name, test_item_suffix) mangled2 = reader2.mangle(test_item_name, test_item_suffix) assert mangled1 == mangled2 def test_repeatable__same_reader(self): test_item_name = "han solo" test_item_suffix = "chewbacca" reader = suds.reader.Reader(suds.options.Options()) mangled1 = reader.mangle(test_item_name, test_item_suffix) mangled2 = reader.mangle(test_item_name, test_item_suffix) assert mangled1 == mangled2 def test_suffix(self): test_item_name = "and a one! and a two! and a one - two - three!" test_item_suffix = "pimpl" reader = suds.reader.Reader(suds.options.Options()) mangled = reader.mangle(test_item_name, test_item_suffix) assert mangled.endswith(test_item_suffix) suds-1.1.2/tests/test_reply_handling.py000066400000000000000000000501671425611400200202470ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Unit tests related to Suds Python library reply processing. Implemented using the 'pytest' testing framework. """ import testutils if __name__ == "__main__": testutils.run_using_pytest(globals()) import suds import pytest from six import itervalues, next, u from six.moves import http_client import xml.sax def test_ACCEPTED_and_NO_CONTENT_status_reported_as_None_with_faults(): client = testutils.client_from_wsdl(_wsdl__simple_f, faults=True) def f(reply, status): inject = {"reply": suds.byte_str(reply), "status": status} return client.service.f(__inject=inject) assert f("", None) is None pytest.raises(Exception, f, "", http_client.INTERNAL_SERVER_ERROR) assert f("", http_client.ACCEPTED) is None assert f("", http_client.NO_CONTENT) is None assert f("bla-bla", http_client.ACCEPTED) is None assert f("bla-bla", http_client.NO_CONTENT) is None def test_ACCEPTED_and_NO_CONTENT_status_reported_as_None_without_faults(): client = testutils.client_from_wsdl(_wsdl__simple_f, faults=False) def f(reply, status): inject = {"reply": suds.byte_str(reply), "status": status} return client.service.f(__inject=inject) assert f("", None) is not None assert f("", http_client.INTERNAL_SERVER_ERROR) is not None assert f("", http_client.ACCEPTED) is None assert f("", http_client.NO_CONTENT) is None assert f("bla-bla", http_client.ACCEPTED) is None assert f("bla-bla", http_client.NO_CONTENT) is None def test_badly_formed_reply_XML(): for faults in (True, False): client = testutils.client_from_wsdl(_wsdl__simple_f, faults=faults) pytest.raises(xml.sax.SAXParseException, client.service.f, __inject={"reply": suds.byte_str("bad food")}) #TODO: Update the current restriction type output parameter handling so such # parameters get converted to the correct Python data type based on the # restriction's underlying data type. @pytest.mark.xfail def test_restriction_data_types(): client_unnamed = testutils.client_from_wsdl(testutils.wsdl("""\ """, output="Elemento")) client_named = testutils.client_from_wsdl(testutils.wsdl("""\ """, output="Elemento")) client_twice_restricted = testutils.client_from_wsdl(testutils.wsdl("""\ """, output="Elemento")) for client in (client_unnamed, client_named, client_twice_restricted): response = client.service.f(__inject=dict(reply=suds.byte_str("""\ 5 """))) assert response.__class__ is int assert response == 5 def test_disabling_automated_simple_interface_unwrapping(): client = testutils.client_from_wsdl(testutils.wsdl("""\ """, output="Wrapper"), unwrap=False) assert not _isOutputWrapped(client, "f") response = client.service.f(__inject=dict(reply=suds.byte_str("""\ La-di-da-da-da """))) assert response.__class__.__name__ == "Wrapper" assert len(response.__class__.__bases__) == 1 assert response.__class__.__bases__[0] is suds.sudsobject.Object assert response.Elemento.__class__ is suds.sax.text.Text assert response.Elemento == "La-di-da-da-da" def test_empty_reply(): client = testutils.client_from_wsdl(_wsdl__simple_f, faults=False) def f(status=None, description=None): inject = dict(reply=suds.byte_str(), status=status, description=description) return client.service.f(__inject=inject) status, reason = f() assert status == http_client.OK assert reason is None status, reason = f(http_client.OK) assert status == http_client.OK assert reason is None status, reason = f(http_client.INTERNAL_SERVER_ERROR) assert status == http_client.INTERNAL_SERVER_ERROR assert reason == "injected reply" status, reason = f(http_client.FORBIDDEN) assert status == http_client.FORBIDDEN assert reason == "injected reply" status, reason = f(http_client.FORBIDDEN, "kwack") assert status == http_client.FORBIDDEN assert reason == "kwack" def test_fault_reply_with_unicode_faultstring(monkeypatch): monkeypatch.delitem(locals(), "e", False) unicode_string = u("\u20AC Jurko Gospodneti\u0107 " "\u010C\u0106\u017D\u0160\u0110" "\u010D\u0107\u017E\u0161\u0111") fault_xml = suds.byte_str(u("""\ env:Client %s """) % (unicode_string,)) client = testutils.client_from_wsdl(_wsdl__simple_f, faults=True) inject = dict(reply=fault_xml, status=http_client.INTERNAL_SERVER_ERROR) e = pytest.raises(suds.WebFault, client.service.f, __inject=inject).value try: assert e.fault.faultstring == unicode_string assert e.document.__class__ is suds.sax.document.Document finally: del e # explicitly break circular reference chain in Python 3 client = testutils.client_from_wsdl(_wsdl__simple_f, faults=False) status, fault = client.service.f(__inject=dict(reply=fault_xml, status=http_client.INTERNAL_SERVER_ERROR)) assert status == http_client.INTERNAL_SERVER_ERROR assert fault.faultstring == unicode_string def test_invalid_fault_namespace(monkeypatch): monkeypatch.delitem(locals(), "e", False) fault_xml = suds.byte_str("""\ env:Client Dummy error. ultimate """) client = testutils.client_from_wsdl(_wsdl__simple_f, faults=False) inject = dict(reply=fault_xml, status=http_client.OK) e = pytest.raises(Exception, client.service.f, __inject=inject).value try: assert e.__class__ is Exception assert str(e) == " not mapped to message part" finally: del e # explicitly break circular reference chain in Python 3 for http_status in (http_client.INTERNAL_SERVER_ERROR, http_client.PAYMENT_REQUIRED): status, reason = client.service.f(__inject=dict(reply=fault_xml, status=http_status, description="trla baba lan")) assert status == http_status assert reason == "trla baba lan" def test_missing_wrapper_response(): """ Suds library's automatic structure unwrapping should not be applied to interpreting received SOAP Response XML. """ client = testutils.client_from_wsdl(testutils.wsdl("""\ """, output="Wrapper")) assert _isOutputWrapped(client, "f") response_with_missing_wrapper = client.service.f(__inject=dict( reply=suds.byte_str(""" Anything """))) assert response_with_missing_wrapper is None def test_reply_error_with_detail_with_fault(monkeypatch): monkeypatch.delitem(locals(), "e", False) client = testutils.client_from_wsdl(_wsdl__simple_f, faults=True) for http_status in (http_client.OK, http_client.INTERNAL_SERVER_ERROR): inject = dict(reply=_fault_reply__with_detail, status=http_status) e = pytest.raises(suds.WebFault, client.service.f, __inject=inject) try: e = e.value _test_fault(e.fault, True) assert e.document.__class__ is suds.sax.document.Document assert str(e) == "Server raised fault: 'Dummy error.'" finally: del e # explicitly break circular reference chain in Python 3 inject = dict(reply=_fault_reply__with_detail, status=http_client.BAD_REQUEST, description="quack-quack") e = pytest.raises(Exception, client.service.f, __inject=inject).value try: assert e.__class__ is Exception assert e.args[0][0] == http_client.BAD_REQUEST assert e.args[0][1] == "quack-quack" finally: del e # explicitly break circular reference chain in Python 3 def test_reply_error_with_detail_without_fault(): client = testutils.client_from_wsdl(_wsdl__simple_f, faults=False) for http_status in (http_client.OK, http_client.INTERNAL_SERVER_ERROR): status, fault = client.service.f(__inject=dict( reply=_fault_reply__with_detail, status=http_status)) assert status == http_client.INTERNAL_SERVER_ERROR _test_fault(fault, True) status, fault = client.service.f(__inject=dict( reply=_fault_reply__with_detail, status=http_client.BAD_REQUEST)) assert status == http_client.BAD_REQUEST assert fault == "injected reply" status, fault = client.service.f(__inject=dict( reply=_fault_reply__with_detail, status=http_client.BAD_REQUEST, description="haleluja")) assert status == http_client.BAD_REQUEST assert fault == "haleluja" def test_reply_error_without_detail_with_fault(monkeypatch): monkeypatch.delitem(locals(), "e", False) client = testutils.client_from_wsdl(_wsdl__simple_f, faults=True) for http_status in (http_client.OK, http_client.INTERNAL_SERVER_ERROR): inject = dict(reply=_fault_reply__without_detail, status=http_status) e = pytest.raises(suds.WebFault, client.service.f, __inject=inject) try: e = e.value _test_fault(e.fault, False) assert e.document.__class__ is suds.sax.document.Document assert str(e) == "Server raised fault: 'Dummy error.'" finally: del e # explicitly break circular reference chain in Python 3 inject = dict(reply=_fault_reply__with_detail, status=http_client.BAD_REQUEST, description="quack-quack") e = pytest.raises(Exception, client.service.f, __inject=inject).value try: assert e.__class__ is Exception assert e.args[0][0] == http_client.BAD_REQUEST assert e.args[0][1] == "quack-quack" finally: del e # explicitly break circular reference chain in Python 3 def test_reply_error_without_detail_without_fault(): client = testutils.client_from_wsdl(_wsdl__simple_f, faults=False) for http_status in (http_client.OK, http_client.INTERNAL_SERVER_ERROR): status, fault = client.service.f(__inject=dict( reply=_fault_reply__without_detail, status=http_status)) assert status == http_client.INTERNAL_SERVER_ERROR _test_fault(fault, False) status, fault = client.service.f(__inject=dict( reply=_fault_reply__without_detail, status=http_client.BAD_REQUEST, description="kung-fu-fui")) assert status == http_client.BAD_REQUEST assert fault == "kung-fu-fui" def test_simple_bare_and_wrapped_output(): # Prepare web service proxies. client_bare = testutils.client_from_wsdl(testutils.wsdl("""\ """, output="fResponse")) client_wrapped = testutils.client_from_wsdl(testutils.wsdl("""\ """, output="Wrapper")) # Make sure suds library inteprets our WSDL definitions as wrapped or bare # output interfaces as expected. assert not _isOutputWrapped(client_bare, "f") assert _isOutputWrapped(client_wrapped, "f") # Both bare & wrapped single parameter output web service operation results # get presented the same way even though the wrapped one actually has an # extra wrapper element around its received output data. data = "The meaning of life." def get_response(client, x): return client.service.f(__inject=dict(reply=suds.byte_str(x))) response_bare = get_response(client_bare, """ %s """ % (data,)) assert response_bare.__class__ is suds.sax.text.Text assert response_bare == data response_wrapped = get_response(client_wrapped, """ %s """ % (data,)) assert response_wrapped.__class__ is suds.sax.text.Text assert response_wrapped == data def test_allow_unknown_message_parts(): # Prepare web service proxies. client = testutils.client_from_wsdl(testutils.wsdl("""\ """, output="Wrapper")) client.set_options(allowUnknownMessageParts=True) data = "The meaning of life." def get_response(client, x): return client.service.f(__inject=dict(reply=suds.byte_str(x))) response = get_response(client, """ %s """ % (data,)) assert response.fResponse == data assert response.gResponse == None # hResponse ignored def test_wrapped_sequence_output(): client = testutils.client_from_wsdl(testutils.wsdl("""\ """, output="Wrapper")) assert _isOutputWrapped(client, "f") response = client.service.f(__inject=dict(reply=suds.byte_str("""\ Uno Due Tre """))) # Composite replies always get unmarshalled as a dynamically constructed # class named 'reply'. assert len(response.__class__.__bases__) == 1 assert response.__class__.__name__ == "reply" assert response.__class__.__bases__[0] is suds.sudsobject.Object # Check response content. assert len(response) == 3 assert response.result1 == "Uno" assert response.result2 == "Due" assert response.result3 == "Tre" assert response.result1.__class__ is suds.sax.text.Text assert response.result2.__class__ is suds.sax.text.Text assert response.result3.__class__ is suds.sax.text.Text def _attributes(object): result = set() for x in object: result.add(x[0]) return result def _isOutputWrapped(client, method_name): assert len(client.wsdl.bindings) == 1 binding = next(itervalues(client.wsdl.bindings)) operation = binding.operations[method_name] return operation.soap.output.body.wrapped def _test_fault(fault, has_detail): assert fault.faultcode == "env:Client" assert fault.faultstring == "Dummy error." assert hasattr(fault, "detail") == has_detail assert not has_detail or fault.detail.errorcode == "ultimate" assert not hasattr(fault, "nonexisting") expected_attributes = set(("faultcode", "faultstring")) if has_detail: expected_attributes.add("detail") assert _attributes(fault) == expected_attributes assert not has_detail or _attributes(fault.detail) == set(("errorcode",)) _fault_reply__with_detail = suds.byte_str("""\ env:Client Dummy error. ultimate """) _fault_reply__without_detail = suds.byte_str("""\ env:Client Dummy error. """) _wsdl__simple_f = testutils.wsdl("""\ """, output="fResponse", operation_name="f") suds-1.1.2/tests/test_request_construction.py000066400000000000000000001232611425611400200215460ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Suds Python library request construction related unit tests. Suds provides the user with an option to automatically 'hide' wrapper elements around simple types and allow the user to specify such parameters without explicitly creating those wrappers. For example: function taking a parameter of type X, where X is a sequence containing only a single simple data type (e.g. string or integer) will be callable by directly passing it that internal simple data type value instead of first wrapping that value in an object of type X and then passing that wrapper object instead. """ import testutils from testutils import _assert_request_content if __name__ == "__main__": testutils.run_using_pytest(globals()) import suds import suds.store import pytest from six import iterkeys, itervalues, next, u #TODO: Update the current restriction type output parameter handling so such # parameters get converted to the correct Python data type based on the # restriction's underlying data type. @pytest.mark.xfail def test_bare_input_restriction_types(): client_unnamed = testutils.client_from_wsdl(testutils.wsdl("""\ """, input="Elemento", operation_name="f")) client_named = testutils.client_from_wsdl(testutils.wsdl("""\ """, input="Elemento", operation_name="f")) assert not _is_input_wrapped(client_unnamed, "f") assert not _is_input_wrapped(client_named, "f") def parametrize_single_element_input_test(param_names, param_values): """ Define different parametrized single element input test function calls. Parameter value input is a tuple containing 2+ parameters: * 1. element - input XSD element definition * 2. element - input element name * 3+ elements - tuples containing the following: * position argument list for the invoked test web service operation * expected request body content for the given arguments * [optional] reason for marking this test case as expected to fail """ mark = pytest expanded_param_values = [] for param_value in param_values: xsd, external_element_name = param_value[0:2] for next_value in param_value[2:]: assert len(next_value) in (2, 3) args, request_body = next_value[:2] xfail = len(next_value) == 3 param = (xsd, external_element_name, args, request_body) # Manually skip xfails for now since there's no way to mark if not xfail: expanded_param_values.append(param) return (param_names, expanded_param_values), {} @pytest.mark.indirect_parametrize(parametrize_single_element_input_test, ("xsd", "external_element_name", "args", "request_body"), ( # Bare non-optional element. ('', "a", ([], ""), ([5], "5")), # Bare optional element. ('', "a", ([], ""), ([5], "5")), # Choice with a non-empty sub-sequence. ("""\ """, "Wrapper", ([], "", "non-optional choice handling buggy"), ([5], "5"), ([None, 1], "1", "non-optional choice handling buggy"), ([None, 1, 2], "12"), ([None, None, 1], "1", "non-optional choice handling buggy")), # Choice with a non-optional element. ("""\ """, "Wrapper", ([], "", "non-optional choice handling buggy"), ([5], "5")), # Choice with an optional element. ("""\ """, "Wrapper", ([], ""), ([5], "5")), # Choices with multiple elements, at least one of which is optional. ("""\ """, "Wrapper", ([], ""), ([5], "5"), ([None, 5], "5")), ("""\ """, "Wrapper", ([], ""), ([5], "5"), ([None, 5], "5")), ("""\ """, "Wrapper", ([], ""), ([5], "5"), ([None, 5], "5")), # Choice with multiple non-empty sub-sequences. ("""\ """, "Wrapper", ([], "", "non-optional choice handling buggy"), ([5], "5", "non-optional choice handling buggy"), ([5, 9], """\ 5 9 """), ([None, 1], "1", "non-optional choice handling buggy"), ([None, None, 1], "1", "non-optional choice handling buggy"), ([None, None, 1, 2], "12"), ([None, None, None, 1], "1", "non-optional choice handling buggy")), # Empty choice. ("""\ """, "Wrapper", ([], "")), # Empty sequence. ("""\ """, "Wrapper", ([], "")), # Optional choice. ("""\ """, "Wrapper", ([], "", # This test passes by accident - the following two bugs seem to # cancel each other out: # - choice order indicators explicitly marked optional unsupported # - not constructing correct input parameter values when using no # input arguments for a choice #"suds does not yet support minOccurs/maxOccurs attributes on all/" #"choice/sequence order indicators"), ), ([5], "5"), ([None, 1], "1")), # Optional sequence. ("""\ """, "Wrapper", ([], "", "suds does not yet support minOccurs/maxOccurs attributes on all/" "choice/sequence order indicators"), ([5], "5"), ([None, 1], "1"), ([1, 2], """\ 1 2 """)), # Sequence with a non-empty sub-sequence. ("""\ """, "Wrapper", ([], ""), ([5], "5"), ([None, 1], "1"), ([None, 1, 2], """\ 1 2 """), ([None, None, 1], "1")), # Sequence with a non-optional element. ("""\ """, "Wrapper", ([], ""), ([5], "5")), # Sequence with an optional element. ("""\ """, "Wrapper", ([], ""), ([5], "5")), # Sequence with multiple consecutive choices. ("""\ """, "Wrapper", ([], "", "non-optional choice handling buggy"), ([5], "5"), ([None, 1, 2], """\ 1 2 """), ([None, 1, None, 2], """\ 1 2 """)), # Sequence with multiple optional elements. ("""\ """, "Wrapper", ([], ""), ([5], "5"), ([None, 1], "1"), ([5, 1], "51")), )) def test_document_literal_request_for_single_element_input(xsd, external_element_name, args, request_body): wsdl = testutils.wsdl(xsd, input=external_element_name, xsd_target_namespace="dr. Doolittle", operation_name="f") client = testutils.client_from_wsdl(wsdl, nosend=True, prettyxml=True) _assert_request_content(client.service.f(*args), """\ %s """ % (request_body,)) def test_disabling_automated_simple_interface_unwrapping(): xsd_target_namespace = "woof" wsdl = testutils.wsdl("""\ """, input="Wrapper", operation_name="f", xsd_target_namespace=xsd_target_namespace) client = testutils.client_from_wsdl(wsdl, nosend=True, prettyxml=True, unwrap=False) assert not _is_input_wrapped(client, "f") element_data = "Wonderwall" wrapper = client.factory.create("my_xsd:Wrapper") wrapper.Elemento = element_data _assert_request_content(client.service.f(Wrapper=wrapper), """\
%s """ % (xsd_target_namespace, element_data)) def test_element_references_to_different_namespaces(): wsdl = suds.byte_str("""\ """) external_schema = suds.byte_str("""\ """) store = suds.store.DocumentStore(external_schema=external_schema, wsdl=wsdl) client = suds.client.Client("suds://wsdl", cache=None, documentStore=store, nosend=True, prettyxml=True) _assert_request_content(client.service.f(local="--L--", local_referenced="--LR--", external="--E--"), """\
--L-- --LR-- --E-- """) def test_function_with_reserved_characters(): wsdl = suds.byte_str("""\ """) store = suds.store.DocumentStore(wsdl=wsdl) client = suds.client.Client("suds://wsdl", cache=None, documentStore=store, nosend=True, prettyxml=True) operation_name = ".f" method = getattr(client.service, operation_name) request = method(local="--L--") _assert_request_content(request, """\
--L-- """) def test_invalid_input_parameter_type_handling(): """ Input parameters of invalid type get silently pushed into the constructed SOAP request as strings, even though the constructed SOAP request does not necessarily satisfy requirements set for it in the web service's WSDL schema. It is then left up to the web service implementation to detect and report this error. """ xsd_target_namespace = "1234567890" wsdl = testutils.wsdl("""\ """, input="Wrapper", operation_name="f", xsd_target_namespace=xsd_target_namespace) client = testutils.client_from_wsdl(wsdl, nosend=True, prettyxml=True) # Passing an unrelated Python type value. class SomeType: def __str__(self): return "Some string representation." _assert_request_content(client.service.f(anInteger=SomeType()), """\
Some string representation. """ % (xsd_target_namespace,)) # Passing a value of a WSDL schema defined type. value = client.factory.create("my_xsd:Freakazoid") value.freak1 = "Tiny" value.freak2 = "Miny" value.freak3 = "Mo" _assert_request_content(client.service.f(anInteger=value), """\
Tiny Miny Mo """ % (xsd_target_namespace,)) def test_missing_parameters(): """Missing non-optional parameters should get passed as empty values.""" xsd_target_namespace = "plonker" service = _service_from_wsdl(testutils.wsdl("""\ """, input="Wrapper", operation_name="f", xsd_target_namespace=xsd_target_namespace)) _assert_request_content(service.f(), """\
""" % (xsd_target_namespace,)) _assert_request_content(service.f((u("Pero \u017Ddero"))), u("""\
Pero \u017Ddero """) % (xsd_target_namespace,)) _assert_request_content(service.f(anInteger=666), """\
666 """ % (xsd_target_namespace,)) # None value is treated the same as undefined. _assert_request_content(service.f(aString=None, anInteger=666), """\
666 """ % (xsd_target_namespace,)) _assert_request_content(service.f(aString="Omega", anInteger=None), """\
Omega """ % (xsd_target_namespace,)) def test_named_parameter(): class Tester: def __init__(self, service, expected_xml): self.service = service self.expected_xml = expected_xml def test(self, *args, **kwargs): request = self.service.f(*args, **kwargs) _assert_request_content(request, self.expected_xml) # Test different ways to make the same web service operation call. xsd_target_namespace = "qwerty" service = _service_from_wsdl(testutils.wsdl("""\ """, input="Wrapper", operation_name="f", xsd_target_namespace=xsd_target_namespace)) t = Tester(service, """\
einz zwei """ % (xsd_target_namespace,)) t.test("einz", "zwei") t.test(uno="einz", due="zwei") t.test(due="zwei", uno="einz") t.test("einz", due="zwei") # The order of parameters in the constructed SOAP request should depend # only on the initial WSDL schema. xsd_target_namespace = "abracadabra" service = _service_from_wsdl(testutils.wsdl("""\ """, input="Wrapper", operation_name="f", xsd_target_namespace=xsd_target_namespace)) t = Tester(service, """\
zwei einz """ % (xsd_target_namespace,)) t.test("zwei", "einz") t.test(uno="einz", due="zwei") t.test(due="zwei", uno="einz") t.test("zwei", uno="einz") def test_optional_parameter_handling(): """Missing optional parameters should not get passed at all.""" xsd_target_namespace = "RoOfIe" service = _service_from_wsdl(testutils.wsdl("""\ """, input="Wrapper", operation_name="f", xsd_target_namespace=xsd_target_namespace)) _assert_request_content(service.f(), """\
""" % (xsd_target_namespace,)) # None is treated as an undefined value. _assert_request_content(service.f(None), """\
""" % (xsd_target_namespace,)) # Empty string values are treated as well defined values. _assert_request_content(service.f(""), """\
""" % (xsd_target_namespace,)) _assert_request_content(service.f("Kiflica"), """\
Kiflica """ % (xsd_target_namespace,)) _assert_request_content(service.f(anInteger=666), """\
666 """ % (xsd_target_namespace,)) _assert_request_content(service.f("Alfa", 9), """\
Alfa 9 """ % (xsd_target_namespace,)) def test_optional_parameter_with_empty_object_value(): """Missing optional parameters should not get passed at all.""" xsd_target_namespace = "I'm a cute little swamp gorilla monster!" wsdl = testutils.wsdl("""\ """, input="Wrapper", operation_name="f", xsd_target_namespace=xsd_target_namespace) client = testutils.client_from_wsdl(wsdl, nosend=True, prettyxml=True) service = client.service # Base line: nothing passed --> nothing marshalled. _assert_request_content(service.f(), """\
""" % (xsd_target_namespace,)) # Passing a empty object as an empty dictionary. _assert_request_content(service.f({}), """\
""" % (xsd_target_namespace,)) # Passing a empty explicitly constructed `suds.sudsobject.Object`. empty_object = client.factory.create("my_xsd:Wrapper") _assert_request_content(service.f(empty_object), """\
""" % (xsd_target_namespace,)) def test_SOAP_headers(): """Rudimentary 'soapheaders' option usage test.""" wsdl = suds.byte_str("""\ """) header_data = "fools rush in where angels fear to tread" client = testutils.client_from_wsdl(wsdl, nosend=True, prettyxml=True) client.options.soapheaders = header_data _assert_request_content(client.service.my_operation(), """\
%s
""" % (header_data,)) def test_twice_wrapped_parameter(): """ Suds does not recognize 'twice wrapped' data structures and unwraps the external one but keeps the internal wrapping structure in place. """ xsd_target_namespace = "spank me" wsdl = testutils.wsdl("""\ """, input="Wrapper1", operation_name="f", xsd_target_namespace=xsd_target_namespace) client = testutils.client_from_wsdl(wsdl, nosend=True, prettyxml=True) assert _is_input_wrapped(client, "f") # Web service operation calls made with 'valid' parameters. # # These calls are actually illegal and result in incorrectly generated SOAP # requests not matching the relevant WSDL schema. To make them valid we # would need to pass a more complex value instead of a simple string, but # the current simpler solution is good enough for what we want to test # here. value = "A B C" expected_request = """\
%s """ % (xsd_target_namespace, value) _assert_request_content(client.service.f(value), expected_request) _assert_request_content(client.service.f(Wrapper2=value), expected_request) # Web service operation calls made with 'invalid' parameters. def test_invalid_parameter(**kwargs): assert len(kwargs) == 1 keyword = next(iterkeys(kwargs)) expected = "f() got an unexpected keyword argument '%s'" % (keyword,) e = pytest.raises(TypeError, client.service.f, **kwargs).value try: assert str(e) == expected finally: del e # explicitly break circular reference chain in Python 3 test_invalid_parameter(Elemento=value) test_invalid_parameter(Wrapper1=value) def test_wrapped_parameter(monkeypatch): monkeypatch.delitem(locals(), "e", False) # Prepare web service proxies. def client(xsd, *input): wsdl = testutils.wsdl(xsd, input=input, xsd_target_namespace="toolyan", operation_name="f") return testutils.client_from_wsdl(wsdl, nosend=True, prettyxml=True) client_bare_single = client("""\ """, "Elemento") client_bare_multiple_simple = client("""\ """, "Elemento1", "Elemento2") client_bare_multiple_wrapped = client("""\ """, "Elemento1", "Elemento2") client_wrapped_unnamed = client("""\ """, "Wrapper") client_wrapped_named = client("""\ """, "Wrapper") # Make sure suds library interprets our WSDL definitions as wrapped or bare # input interfaces as expected. assert not _is_input_wrapped(client_bare_single, "f") assert not _is_input_wrapped(client_bare_multiple_simple, "f") assert not _is_input_wrapped(client_bare_multiple_wrapped, "f") assert _is_input_wrapped(client_wrapped_unnamed, "f") assert _is_input_wrapped(client_wrapped_named, "f") # Both bare & wrapped single parameter input web service operations get # called the same way even though the wrapped one actually has an extra # wrapper element around its input data. data = "Maestro" def call_single(c): return c.service.f(data) _assert_request_content(call_single(client_bare_single), """\
%s """ % (data,)) expected_xml = """\
%s """ % (data,) _assert_request_content(call_single(client_wrapped_unnamed), expected_xml) _assert_request_content(call_single(client_wrapped_named), expected_xml) # Suds library's automatic structure unwrapping prevents us from specifying # the external wrapper structure directly. e = pytest.raises(TypeError, client_wrapped_unnamed.service.f, Wrapper="A") try: expected = "f() got an unexpected keyword argument 'Wrapper'" assert str(e.value) == expected finally: del e # explicitly break circular reference chain in Python 3 # Multiple parameter web service operations are never automatically # unwrapped. data = ("Unga", "Bunga") def call_multiple(c): return c.service.f(*data) _assert_request_content(call_multiple(client_bare_multiple_simple), """\
%s %s """ % data) _assert_request_content(call_multiple(client_bare_multiple_wrapped), """\
%s %s """ % data) ############################################################################### # # Test utilities. # ############################################################################### def _is_input_wrapped(client, method_name): assert len(client.wsdl.bindings) == 1 binding = next(itervalues(client.wsdl.bindings)) operation = binding.operations[method_name] return operation.soap.input.body.wrapped def _service_from_wsdl(wsdl): """ Construct a suds Client service instance used in tests in this module. The constructed Client instance only prepares web service operation invocation requests and does not attempt to actually send them. """ return testutils.client_from_wsdl(wsdl, nosend=True, prettyxml=True).service suds-1.1.2/tests/test_sax_document.py000066400000000000000000000053061425611400200177340ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Suds SAX Document unit tests. Implemented using the 'pytest' testing framework. """ if __name__ == "__main__": import testutils testutils.run_using_pytest(globals()) import suds from suds.sax.document import Document import suds.sax.parser import pytest import six import re import sys class TestStringRepresentation: @staticmethod def create_test_document(): input_data = suds.byte_str("""\ """) document = suds.sax.parser.Parser().parse(suds.BytesIO(input_data)) assert document.__class__ is Document return document @pytest.mark.skipif(sys.version_info >= (3,), reason="Python 2 specific") def test_convert_to_byte_str(self): document = self.create_test_document() expected = suds.byte_str(document.str()) assert str(document) == expected def test_convert_to_unicode(self): document = self.create_test_document() expected = document.str() assert six.text_type(document) == expected def test_plain_method(self): document = self.create_test_document() expected = Document.DECL + document.root().plain() result = document.plain() assert result == expected def test_str_method(self): document = self.create_test_document() expected = Document.DECL + "\n" + document.root().str() result = document.str() assert result == expected def test_xml_declaration(self): assert Document.DECL == '' suds-1.1.2/tests/test_sax_element.py000066400000000000000000000142231425611400200175450ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Suds SAX Element unit tests. Implemented using the 'pytest' testing framework. """ if __name__ == "__main__": import testutils testutils.run_using_pytest(globals()) import suds from suds.sax.element import Element import suds.sax.parser import pytest import six import re import sys class TestChildAtPath: def test_backslash_as_path_separator(self): name1 = "child" name2 = "grandchild" root = self.__create_single_branch("root", name1, name2)[0] result = root.childAtPath(name1 + "\\" + name2) assert result is None def test_backslash_in_name(self): root, a, _, _ = self.__create_single_branch("root", "a", "b", "c") b_c = Element("b\\c") a.append(b_c) result = root.childAtPath("a/b\\c") assert result is b_c def test_child_leaf(self): root, child = self.__create_single_branch("root", "child") result = root.childAtPath("child") assert result is child def test_child_not_leaf(self): root, child, _ = self.__create_single_branch("root", "child", "grandchild") result = root.childAtPath("child") assert result is child def test_grandchild_leaf(self): root, _, grandchild = self.__create_single_branch("root", "child", "grandchild") result = root.childAtPath("child/grandchild") assert result is grandchild def test_grandchild_not_leaf(self): root, _, grandchild, _ = self.__create_single_branch("root", "child", "grandchild", "great grandchild") result = root.childAtPath("child/grandchild") assert result is grandchild def test_misplaced(self): root = self.__create_single_branch("root", "a", "x", "b")[0] result = root.childAtPath("a/b") assert result is None def test_missing(self): root = Element("root") result = root.childAtPath("an invalid path") assert result is None def test_name_including_spaces(self): root, _, child, _ = self.__create_single_branch("root", "dumbo", "foo - bar", "baz") result = root.childAtPath("dumbo/foo - bar") assert result is child @pytest.mark.parametrize("n", (2, 3)) def test_repeated_path_separators(self, n): root, child, grandchild = self.__create_single_branch("root", "child", "grandchild") sep = "/" * n path = "child" + sep + "grandchild" result = root.childAtPath(path) assert result is grandchild def test_same_named(self): root, _, child, _ = self.__create_single_branch("root", "a", "a", "a") result = root.childAtPath("a/a") assert result is child @staticmethod def __create_single_branch(*args): """ Construct a single branch element tree with given element names. Returns a list of constructed Element nodes from root to leaf. """ result = [] parent = None for name in args: e = Element(name) result.append(e) if parent is not None: parent.append(e) parent = e return result class TestStringRepresentation: # Must be consistent with how Element.str() formats this data. str_formatted_xml = """\ """ @staticmethod def create_test_element(content=str_formatted_xml): input_data = suds.byte_str(content) xml = suds.sax.parser.Parser().parse(suds.BytesIO(input_data)) element = xml.root() assert element.__class__ is Element return element @pytest.mark.skipif(sys.version_info >= (3,), reason="Python 2 specific") def test_convert_to_byte_str(self): element = self.create_test_element() expected = suds.byte_str(element.str()) assert str(element) == expected def test_convert_to_unicode(self): element = self.create_test_element() expected = element.str() assert six.text_type(element) == expected def test_plain_method(self): element = self.create_test_element(self.str_formatted_xml) expected = re.sub(r"\s*[\r\n]\s*", "", self.str_formatted_xml) result = element.plain() assert result == expected def test_str_method(self): element = self.create_test_element(self.str_formatted_xml) result = element.str() assert result == self.str_formatted_xml @pytest.mark.parametrize("name, expected_prefix, expected_name", ( ("", None, ""), ("bazinga", None, "bazinga"), ("test element name", None, "test element name"), ("aaa:bbb", "aaa", "bbb"), ("aaa:", "aaa", ""), (":aaa", "", "aaa"), ("aaa::bbb", "aaa", ":bbb"), ("aaa:bbb:ccc", "aaa", "bbb:ccc"))) def test_init_name(name, expected_prefix, expected_name): e = Element(name) assert e.prefix == expected_prefix assert e.name == expected_name suds-1.1.2/tests/test_sax_encoder.py000066400000000000000000000142711425611400200175360ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Suds SAX module's special character encoder unit tests. Implemented using the 'pytest' testing framework. """ if __name__ == "__main__": import testutils testutils.run_using_pytest(globals()) import suds.sax.enc import pytest """Data not affected by encode()/decode() operations.""" invariant_decoded_encoded_test_data = ( # Empty text. "", # Pure text. "x", "xyz", "Devil take the hindmost", # Whitespace handling. "spaces kept", " ", " ", "\t", "\v", "\f", "\r", "\n", " \t\t\v\v \f\f \v\v\f\r\n\n\r \t \t\t", " \t\f\v something \r\r\n\freal\f\f\r\n\f\t\f") """ Data that should not be affected by encode()/decode() operations but for which the current encode()/decode() operation implementations are broken. """ invariant_decoded_encoded_test_data__broken = ( # CDATA handling. " \t\r\n\v\f < > ' " &]]>", " \\< \\> \\' \\" ]\\&]]>", "<><><>>>>><<<< This is !!!&& & a test...." "<<>>>>>>>>}}>]?]>>>>]]>") """ Data that should not be affected by encode()/decode() operations but for which the current encode() operation implementation is broken. """ invariant_decoded_encoded_test_data__broken_encode = ( # CDATA handling. "", "", "]]>", "", "", "]]>") """ Decoded/encoded data convertible in either direction using encode()/decode() operations. """ symmetric_decoded_encoded_test_data = [ # Simple replacements. ("<", "<"), (">", ">"), ("'", "'"), ('"', """), ("&", "&"), # Mixed replacements. ("abcd&&<<", "abcd&&<<"), # Character reference lookalikes. ("& lt;", "& lt;"), ("> ;", "&gt ;"), ("&a pos;", "&a pos;"), ("&walle;", "&walle;"), (""", "&quot"), ("&quo", "&quo"), ("amp;", "amp;"), # XML markup. ("unga-bunga", "<a>unga-bunga</a>"), ("", "<a></b>"), ("<&>", "<&></\n>"), (" && ", "<a id="fluffy's"> && </a>"), # CDATA handling. ("< ![CDATA[&]]>", "< ![CDATA[&]]>"), ("", "<! [CDATA[&]]>"), ("", "<![ CDATA[&]]>"), ("", "<![CDATA [&]]>")] + [ # Invarant data. (x, x) for x in invariant_decoded_encoded_test_data] """ Decoded/encoded data that should be convertible in either direction using encode()/decode() operations but for which the current encode()/decode() operation implementations are broken. """ symmetric_decoded_encoded_test_data__broken = [ # CDATA handling. (x, x) for x in invariant_decoded_encoded_test_data__broken] """ Decoded/encoded data that should be convertible in either direction using encode()/decode() operations but for which the current encode() operation implementation is broken. """ symmetric_decoded_encoded_test_data__broken_encode = [ # CDATA handling. (x, x) for x in invariant_decoded_encoded_test_data__broken_encode] + [ ("]]>", "<a>]]></a>"), ("&&]]>&", "&&]]>&")] @pytest.mark.parametrize(("input", "expected"), [ (e, d) for d, e in symmetric_decoded_encoded_test_data + symmetric_decoded_encoded_test_data__broken_encode] + [ pytest.param(e, d, marks=pytest.mark.xfail(reason="CDATA encoding not supported yet")) for d, e in symmetric_decoded_encoded_test_data__broken] + [ # Character reference lookalikes. (x, x) for x in ( "& lt;", "> ;", "&a pos;", "&walle;", """, "&quo", "amp;")] + [ # Double decode. ("&amp;", "&"), ("&lt;", "<"), ("&gt;", ">"), ("&apos;", "'"), ("&quot;", """)]) def test_decode(input, expected): assert suds.sax.enc.Encoder().decode(input) == expected @pytest.mark.parametrize(("input", "expected"), symmetric_decoded_encoded_test_data + [ pytest.param(x, y, marks=pytest.mark.xfail(reason="CDATA encoding not supported yet")) for x, y in symmetric_decoded_encoded_test_data__broken + symmetric_decoded_encoded_test_data__broken_encode] + [ # Double encoding. #TODO: See whether this 'avoid double encoding' behaviour is actually # desirable. That is how XML entity reference encoding has been implemented # in the original suds implementation, but it makes encode/decode # operations asymmetric and prevents the user from actually encoding data # like '&' that should not be interpreted as '&'. ("&", "&"), ("<", "<"), (">", ">"), ("'", "'"), (""", """)]) def test_encode(input, expected): assert suds.sax.enc.Encoder().encode(input) == expected suds-1.1.2/tests/test_suds.py000066400000000000000000003063461425611400200162310ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ General suds Python library unit tests. Implemented using the 'pytest' testing framework. This whole module should be refactored into more specialized modules as more tests get added to it and it acquires more structure. """ import testutils from testutils import _assert_request_content if __name__ == "__main__": testutils.run_using_pytest(globals()) import suds import suds.store from suds.sax.text import Raw from testutils.compare_sax import CompareSAX import pytest from six import b, itervalues, next import re import xml.sax @pytest.fixture def client_with_optional_array_parameters(): wsdl = b("""\ """) client = testutils.client_from_wsdl(wsdl, nosend=True, prettyxml=True) return client #TODO: Update the current choice parameter handling implementation to make this # test pass. @pytest.mark.xfail def test_choice_parameter_implementation_inconsistencies(): """ Choice parameter support implementation needs to be cleaned up. If you declare a message part's element of a simple type X, or you define it as a complex type having a single member of type X, and suds has been configured to automatically unwrap such single-member complex types, the web service proxy object's constructed function declarations should match. They should both accept a single parameter of type X. However the current choice support implementation causes only the 'complex' case to get an additional 'choice' flag information to be included in the constructed parameter definition structure. """ def client(x, y): return testutils.client_from_wsdl(testutils.wsdl(x, input=y)) client_simple_short = client("""\ """, "Elemento") client_simple_long = client("""\ """, "Elemento") client_complex_wrapped = client("""\ """, "Wrapper") method_param = lambda x : x.sd[0].ports[0][1][0][1][0] method_param_simple_short = method_param(client_simple_short) method_param_simple_long = method_param(client_simple_long) method_param_complex_wrapped = method_param(client_complex_wrapped) assert len(method_param_simple_short) == len(method_param_simple_long) assert len(method_param_simple_long) == len(method_param_complex_wrapped) def test_converting_client_to_string_must_not_raise_an_exception(): client = testutils.client_from_wsdl( b("")) str(client) def test_converting_metadata_to_string(): client = testutils.client_from_wsdl(b("""\ """)) # Metadata with empty content. metadata = client.wsdl.__metadata__ assert len(metadata) == 0 assert "" == str(metadata) # Metadata with non-empty content. metadata = client.factory.create("AAA").__metadata__ assert len(metadata) == 2 metadata_string = str(metadata) assert re.search(" sxtype = ", metadata_string) assert re.search(r" ordering\[\] = ", metadata_string) def test_empty_invalid_WSDL(monkeypatch): monkeypatch.delitem(locals(), "e", False) e = pytest.raises(xml.sax.SAXParseException, testutils.client_from_wsdl, b("")) try: assert e.value.getMessage() == "no element found" finally: del e # explicitly break circular reference chain in Python 3 def test_empty_valid_WSDL(): client = testutils.client_from_wsdl( b("")) assert not client.wsdl.services, "No service definitions must be read " \ "from an empty WSDL." def test_enumeration_type_string_should_contain_its_value(): client = testutils.client_from_wsdl(b("""\ """)) enumeration_data = client.wsdl.schema.types["AAA", "my-namespace"] # Legend: # eX - enumeration element. # aX - ancestry for the enumeration element. (e1, a1), (e2, a2), (e3, a3) = enumeration_data assert isinstance(e1, suds.xsd.sxbasic.Enumeration) assert isinstance(e2, suds.xsd.sxbasic.Enumeration) assert isinstance(e3, suds.xsd.sxbasic.Enumeration) assert e1.name == "One" assert e2.name == "Two" assert e3.name == "Thirty-Two" # Python 3 output does not include a trailing L after long integer output, # while Python 2 does. For example: 0x12345678 is output as 0x12345678L in # Python 2 and simply as 0x12345678 in Python 3. assert re.match('$', e1.str()) assert re.match('$', e2.str()) assert re.match('$', e3.str()) def test_function_parameters_global_sequence_in_a_sequence(): client = testutils.client_from_wsdl(b("""\ """)) service = client.sd[0] assert len(service.types) == 1 # Method parameters as read from the service definition. assert len(service.params) == 3 assert service.params[0][0].name == "x1" assert service.params[0][0].type == _string_type assert isinstance(service.params[0][1], suds.xsd.sxbuiltin.XString) assert service.params[1][0].name == "x2" assert service.params[1][0].type == ("UngaBunga", "my-namespace") assert isinstance(service.params[1][1], suds.xsd.sxbasic.Complex) assert service.params[2][0].name == "x3" assert service.params[2][0].type == _string_type assert isinstance(service.params[2][1], suds.xsd.sxbuiltin.XString) # Method parameters as read from a method object. assert len(service.ports) == 1 port, methods = service.ports[0] assert len(methods) == 1 method_name, method_params = methods[0] assert method_name == "f" assert len(method_params) == 3 assert method_params[0][0] == "x1" assert method_params[0][1] is service.params[0][0] assert method_params[1][0] == "x2" assert method_params[1][1] is service.params[1][0] assert method_params[2][0] == "x3" assert method_params[2][1] is service.params[2][0] def test_function_parameters_local_choice(): client = testutils.client_from_wsdl(b("""\ """)) service = client.sd[0] assert not service.types # Method parameters as read from the service definition. assert len(service.params) == 2 assert service.params[0][0].name == "u1" assert service.params[0][0].type == _string_type assert isinstance(service.params[0][1], suds.xsd.sxbuiltin.XString) assert service.params[1][0].name == "u2" assert service.params[1][0].type == _string_type assert isinstance(service.params[1][1], suds.xsd.sxbuiltin.XString) # Method parameters as read from a method object. assert len(service.ports) == 1 port, methods = service.ports[0] assert len(methods) == 1 method_name, method_params = methods[0] assert method_name == "f" assert len(method_params) == 2 assert method_params[0][0] == "u1" assert method_params[0][1] is service.params[0][0] assert method_params[1][0] == "u2" assert method_params[1][1] is service.params[1][0] # Construct method parameter element object. param_out = client.factory.create("Elemento") _assert_dynamic_type(param_out, "Elemento") assert not param_out.__keylist__ def test_function_parameters_local_choice_in_a_sequence(): client = testutils.client_from_wsdl(b("""\ """)) service = client.sd[0] assert not service.types # Method parameters as read from the service definition. assert len(service.params) == 3 assert service.params[0][0].name == "x1" assert service.params[0][0].type == _string_type assert isinstance(service.params[0][1], suds.xsd.sxbuiltin.XString) assert service.params[1][0].name == "x2" assert service.params[1][0].type is None assert isinstance(service.params[1][1], suds.xsd.sxbasic.Element) assert service.params[2][0].name == "x3" assert service.params[2][0].type == _string_type assert isinstance(service.params[2][1], suds.xsd.sxbuiltin.XString) # Method parameters as read from a method object. assert len(service.ports) == 1 port, methods = service.ports[0] assert len(methods) == 1 method_name, method_params = methods[0] assert method_name == "f" assert len(method_params) == 3 assert method_params[0][0] == "x1" assert method_params[0][1] is service.params[0][0] assert method_params[1][0] == "x2" assert method_params[1][1] is service.params[1][0] assert method_params[2][0] == "x3" assert method_params[2][1] is service.params[2][0] # Construct method parameter element object. param_out = client.factory.create("Elemento") _assert_dynamic_type(param_out, "Elemento") assert param_out.x1 is None _assert_dynamic_type(param_out.x2, "x2") assert not param_out.x2.__keylist__ assert param_out.x3 is None # Construct method parameter objects with a locally defined type. param_in = client.factory.create("Elemento.x2") _assert_dynamic_type(param_in, "x2") assert not param_out.x2.__keylist__ assert param_in is not param_out.x2 def test_function_parameters_local_sequence_in_a_sequence(): client = testutils.client_from_wsdl(b("""\ """)) service = client.sd[0] assert not service.types # Method parameters as read from the service definition. assert len(service.params) == 3 assert service.params[0][0].name == "x1" assert service.params[0][0].type == _string_type assert isinstance(service.params[0][1], suds.xsd.sxbuiltin.XString) assert service.params[1][0].name == "x2" assert service.params[1][0].type is None assert isinstance(service.params[1][1], suds.xsd.sxbasic.Element) assert service.params[2][0].name == "x3" assert service.params[2][0].type == _string_type assert isinstance(service.params[2][1], suds.xsd.sxbuiltin.XString) # Method parameters as read from a method object. assert len(service.ports) == 1 port, methods = service.ports[0] assert len(methods) == 1 method_name, method_params = methods[0] assert method_name == "f" assert len(method_params) == 3 assert method_params[0][0] == "x1" assert method_params[0][1] is service.params[0][0] assert method_params[1][0] == "x2" assert method_params[1][1] is service.params[1][0] assert method_params[2][0] == "x3" assert method_params[2][1] is service.params[2][0] # Construct method parameter element object. param_out = client.factory.create("Elemento") _assert_dynamic_type(param_out, "Elemento") assert param_out.x1 is None _assert_dynamic_type(param_out.x2, "x2") assert param_out.x2.u1 is None assert param_out.x2.u2 is None assert param_out.x2.u3 is None assert param_out.x3 is None # Construct method parameter objects with a locally defined type. param_in = client.factory.create("Elemento.x2") _assert_dynamic_type(param_in, "x2") assert param_in.u1 is None assert param_in.u2 is None assert param_in.u3 is None assert param_in is not param_out.x2 def test_function_parameters_sequence_in_a_choice(): client = testutils.client_from_wsdl(b("""\ """)) # Input #1. request = _construct_SOAP_request(client, 'f', a1="Wackadoodle") CompareSAX.document2data(request, """\
Wackadoodle """) # Input #2. param = client.factory.create("Choice.sequence") param.e2 = "Wackadoodle" request = _construct_SOAP_request(client, 'f', sequence=param) CompareSAX.document2data(request, """\
Wackadoodle """) def test_function_parameters_sequence_in_a_choice_in_a_sequence(): client = testutils.client_from_wsdl(b("""\ """)) # Construct input parameters. param = client.factory.create("External.choice") param.sequence = client.factory.create("External.choice.sequence") param.sequence.e2 = "Wackadoodle" # Construct a SOAP request containing our input parameters. request = _construct_SOAP_request(client, 'f', param) CompareSAX.document2data(request, """\
Wackadoodle """) def test_function_parameters_strings(): client = testutils.client_from_wsdl(b("""\ """)) service = client.sd[0] assert not service.types # Method parameters as read from the service definition. assert len(service.params) == 3 assert service.params[0][0].name == "x1" assert service.params[0][0].type == _string_type assert isinstance(service.params[0][1], suds.xsd.sxbuiltin.XString) assert service.params[1][0].name == "x2" assert service.params[1][0].type == _string_type assert isinstance(service.params[1][1], suds.xsd.sxbuiltin.XString) assert service.params[2][0].name == "x3" assert service.params[2][0].type == _string_type assert isinstance(service.params[2][1], suds.xsd.sxbuiltin.XString) # Method parameters as read from a method object. assert len(service.ports) == 1 port, methods = service.ports[0] assert len(methods) == 1 method_name, method_params = methods[0] assert method_name == "f" assert len(method_params) == 3 assert method_params[0][0] == "x1" assert method_params[0][1] is service.params[0][0] assert method_params[1][0] == "x2" assert method_params[1][1] is service.params[1][0] assert method_params[2][0] == "x3" assert method_params[2][1] is service.params[2][0] def test_global_enumeration(): client = testutils.client_from_wsdl(b("""\ """)) assert len(client.sd) == 1 service = client.sd[0] assert len(service.types) == 1 for typeTuple in service.types: # Tuple containing the same object twice. assert len(typeTuple) == 2 assert typeTuple[0] is typeTuple[1] aType = service.types[0][0] assert isinstance(aType, suds.xsd.sxbasic.Simple) assert aType.name == "AAA" assert aType.enum() assert aType.mixed() assert aType.restriction() assert not aType.choice() assert not aType.sequence() assert len(aType.rawchildren) == 1 assert isinstance(aType.rawchildren[0], suds.xsd.sxbasic.Restriction) assert aType.rawchildren[0].ref == _string_type enum = client.factory.create("AAA") assert enum.One == "One" assert enum.Two == "Two" assert getattr(enum, "Thirty-Two") == "Thirty-Two" def test_global_sequence_in_a_global_sequence(): client = testutils.client_from_wsdl(b("""\ """)) service = client.sd[0] assert len(service.types) == 2 for typeTuple in service.types: # Tuple containing the same object twice. assert len(typeTuple) == 2 assert typeTuple[0] is typeTuple[1] type_in = service.types[0][0] assert isinstance(type_in, suds.xsd.sxbasic.Complex) assert type_in.name == "Oklahoma" assert not type_in.sequence() assert type_in.rawchildren[0].sequence() type_out = service.types[1][0] assert isinstance(type_out, suds.xsd.sxbasic.Complex) assert type_out.name == "Wackadoodle" assert not type_out.sequence() assert type_out.rawchildren[0].sequence() assert len(type_out.rawchildren) == 1 children = type_out.children() assert isinstance(children, list) assert len(children) == 3 assert children[0][0].name == "x1" assert children[0][0].type == _string_type assert children[1][0].name == "x2" assert children[1][0].type == ("Oklahoma", "my-namespace") assert children[2][0].name == "x3" assert children[2][0].type == _string_type sequence_out = client.factory.create("Wackadoodle") _assert_dynamic_type(sequence_out, "Wackadoodle") assert sequence_out.__metadata__.sxtype is type_out assert sequence_out.x1 is None sequence_in = sequence_out.x2 assert sequence_out.x3 is None _assert_dynamic_type(sequence_in, "Oklahoma") assert sequence_in.__metadata__.sxtype is type_in assert sequence_in.c1 is None assert sequence_in.c2 is None assert sequence_in.c3 is None def test_global_string_sequence(): client = testutils.client_from_wsdl(b("""\ """)) service = client.sd[0] assert len(service.types) == 1 for typeTuple in service.types: # Tuple containing the same object twice. assert len(typeTuple) == 2 assert typeTuple[0] is typeTuple[1] aType = service.types[0][0] assert isinstance(aType, suds.xsd.sxbasic.Complex) assert aType.name == "Oklahoma" assert not aType.choice() assert not aType.enum() assert not aType.mixed() assert not aType.restriction() assert not aType.sequence() assert len(aType.rawchildren) == 1 sequence_node = aType.rawchildren[0] assert isinstance(sequence_node, suds.xsd.sxbasic.Sequence) assert sequence_node.sequence() assert len(sequence_node) == 3 sequence_items = sequence_node.children() assert isinstance(sequence_items, list) assert len(sequence_items) == 3 assert sequence_items[0][0].name == "c1" assert sequence_items[0][0].type == _string_type assert sequence_items[1][0].name == "c2" assert sequence_items[1][0].type == _string_type assert sequence_items[2][0].name == "c3" assert sequence_items[2][0].type == _string_type sequence = client.factory.create("Oklahoma") getattr(sequence, "c1") getattr(sequence, "c2") getattr(sequence, "c3") pytest.raises(AttributeError, getattr, sequence, "nonExistingChild") assert sequence.c1 is None assert sequence.c2 is None assert sequence.c3 is None sequence.c1 = "Pero" sequence.c3 = "Zdero" assert sequence.c1 == "Pero" assert sequence.c2 is None assert sequence.c3 == "Zdero" def test_local_sequence_in_a_global_sequence(): client = testutils.client_from_wsdl(b("""\ """)) service = client.sd[0] assert len(service.types) == 1 type_out = service.types[0][0] assert isinstance(type_out, suds.xsd.sxbasic.Complex) assert type_out.name == "Wackadoodle" assert not type_out.sequence() assert type_out.rawchildren[0].sequence() children = type_out.children() assert isinstance(children, list) assert len(children) == 2 type_in1 = children[0][0] assert isinstance(type_in1, suds.xsd.sxbasic.Element) assert not type_in1.sequence() assert type_in1.rawchildren[0].rawchildren[0].sequence() type_in2 = children[1][0] assert isinstance(type_in2, suds.xsd.sxbasic.Element) assert not type_in2.sequence() assert type_in2.rawchildren[0].rawchildren[0].sequence() assert type_in1.rawchildren[0].name == "Oklahoma" assert type_in1.rawchildren[0].type is None namespace1 = type_in1.rawchildren[0].namespace() assert namespace1 == ("ns", "my-namespace") assert type_in2.rawchildren[0].name is None assert type_in2.rawchildren[0].type is None assert type_in1.rawchildren[0].namespace() is namespace1 sequence_out = client.factory.create("Wackadoodle") _assert_dynamic_type(sequence_out, "Wackadoodle") assert sequence_out.__metadata__.sxtype is type_out sequence_in1 = sequence_out.x1 sequence_in2 = sequence_out.x2 _assert_dynamic_type(sequence_in1, "x1") _assert_dynamic_type(sequence_in2, "x2") assert sequence_in1.__metadata__.sxtype is type_in1 assert sequence_in2.__metadata__.sxtype is type_in2 assert sequence_in1.c1 is None assert sequence_in1.c2 is None assert sequence_in1.c3 is None assert sequence_in2.s is None def test_optional_parameter_not_instantiated(): wsdl = b("""\ """) expected_request_content = """\ """ client = testutils.client_from_wsdl(wsdl, nosend=True, prettyxml=True) foobar = client.factory.create("foobar") assert foobar.Optional1 is None assert foobar.Optional2 is None assert foobar.NonOptional1 is not None assert foobar.NonOptional1.Optional1 is None service = client.service result = service.f(foobar=foobar) _assert_request_content(result, expected_request_content) def test_array_are_instantiated_even_if_optional(client_with_optional_array_parameters): expected_request_content = """\ foo bar """ client = client_with_optional_array_parameters foobar = client.factory.create("foobar") assert foobar.Optional1 is None assert foobar.NonOptional1 is not None assert isinstance(foobar.BarArray, list) bar = client.factory.create("Bar") bar.bar = "foo" foobar.BarArray.append(bar) bar = client.factory.create("Bar") bar.bar = 'bar' foobar.BarArray.append(bar) service = client.service result = service.f(FooBar=foobar) _assert_request_content(result, expected_request_content) def test_empty_optional_array_is_not_present(client_with_optional_array_parameters): expected_request_content = """\ """ client = client_with_optional_array_parameters foobar = client.factory.create("foobar") assert foobar.Optional1 is None assert foobar.NonOptional1 is not None assert isinstance(foobar.BarArray, list) service = client.service result = service.f(FooBar=foobar) _assert_request_content(result, expected_request_content) def test_named_parameter_with_underscore(): client = testutils.client_from_wsdl(b("""\ Invia le informazioni definitive di chiusura delle spedizioni (Esegue la trasmissione in sede.) Invia le informazioni definitive di chiusura delle spedizioni (Esegue la trasmissione in sede.) Invia le informazioni definitive di chiusura delle spedizioni (Esegue la trasmissione in sede.) """), nosend=True, prettyxml=True) xml = Raw(""" XX xxx xxx 11111111 """) result = client.service.CloseWorkDayByShipmentNumber(xml) expected_request_content = """\ XX xxx xxx 11111111 """ _assert_request_content(result, expected_request_content) def test_attributes_on_elements(): client = testutils.client_from_wsdl(b("""\ """), nosend=True) foobar = client.factory.create("foobar") assert foobar.value is None assert foobar._name == "" foobar.value = "foo" foobar._name = "bar" result = client.service.f(foobar) expected_request_content = """\ foo """ _assert_request_content(result, expected_request_content) def test_no_trailing_comma_in_function_prototype_description_string__0(): client = testutils.client_from_wsdl(b("""\ """)) s = str(client) assert " f()\n" in s def test_no_trailing_comma_in_function_prototype_description_string__1(): client = testutils.client_from_wsdl(b("""\ """)) s = str(client) assert " f(xs:string x1)\n" in s def test_no_trailing_comma_in_function_prototype_description_string__3(): client = testutils.client_from_wsdl(b("""\ """)) s = str(client) assert " f(xs:string x1, xs:string x2, xs:string x3)\n" in s def test_no_types(): client = testutils.client_from_wsdl(b("""\ """)) assert len(client.sd) == 1 service = client.sd[0] assert not client.wsdl.schema.types assert not service.types pytest.raises(suds.TypeNotFound, client.factory.create, "NonExistingType") def test_parameter_referencing_missing_element(monkeypatch): wsdl = testutils.wsdl("", input="missingElement", xsd_target_namespace="aaa") monkeypatch.delitem(locals(), "e", False) e = pytest.raises(suds.TypeNotFound, testutils.client_from_wsdl, wsdl) try: assert str(e.value) == "Type not found: '(missingElement, aaa, )'" finally: del e # explicitly break circular reference chain in Python 3 class TestFindTypeInXSDSchemaWithRecursiveTypeDefinitions: """Must not cause an infinite loop.""" def test_with_recursion_in_imported_schema(self): client = testutils.client_from_wsdl(b("""\ """)) assert pytest.raises( suds.TypeNotFound, client.factory.create, 'ns:DoesNotExist') def test_with_recursion_in_same_schema(self): client = testutils.client_from_wsdl(b("""\ """)) assert pytest.raises( suds.TypeNotFound, client.factory.create, 'ns:DoesNotExist') def test_recursive_XSD_import(): url_xsd = "suds://xsd" xsd = b("""\ """ % dict(url_imported=url_xsd)) wsdl = b("""\ """ % dict(url_imported=url_xsd)) store = suds.store.DocumentStore(xsd=xsd) testutils.client_from_wsdl(wsdl, documentStore=store) #TODO: Update the current restriction type input parameter handling so they get # 'unwrapped' correctly instead of each of their enumeration values getting # interpreted as a separate input parameter. @pytest.mark.xfail def test_restrictions(): client_unnamed = testutils.client_from_wsdl(testutils.wsdl("""\ """, input="Elemento")) client_named = testutils.client_from_wsdl(testutils.wsdl("""\ """, input="Elemento")) client_twice_restricted = testutils.client_from_wsdl(testutils.wsdl("""\ """, input="Elemento")) element_qref = ("Elemento", "my-namespace") type_named_qref = ("MyType", "my-namespace") element_unnamed = client_unnamed.wsdl.schema.elements[element_qref] element_named = client_named.wsdl.schema.elements[element_qref] element_twice_restricted = client_twice_restricted.wsdl.schema.elements[ element_qref] type_unnamed = element_unnamed.resolve() type_named = element_named.resolve() type_twice_restricted = element_twice_restricted.resolve() assert type_unnamed is element_unnamed assert type_named is client_named.wsdl.schema.types[type_named_qref] assert type_twice_restricted is client_twice_restricted.wsdl.schema.types[ type_named_qref] # Regression test against suds automatically unwrapping input parameter # type's enumeration values as separate parameters. params_unnamed = client_unnamed.sd[0].params params_named = client_named.sd[0].params params_twice_restricted = client_twice_restricted.sd[0].params assert len(params_unnamed) == 1 assert len(params_named) == 1 assert len(params_twice_restricted) == 1 assert params_unnamed[0][0] is element_unnamed assert params_unnamed[0][1] is type_unnamed assert params_named[0][0] is element_named assert params_named[0][1] is type_named assert params_twice_restricted[0][0] is element_twice_restricted assert params_twice_restricted[0][1] is type_twice_restricted def test_schema_node_occurrences(): client = testutils.client_from_wsdl(b("""\ """ + _element_node_xml("AnElement1") + _element_node_xml("AnElement2", min=1) + _element_node_xml("AnElement3", max=1) + _element_node_xml("AnOptionalElement1", min=0) + _element_node_xml("AnOptionalElement2", min=0, max=1) + _element_node_xml("Array_0_2", min=0, max=2) + _element_node_xml("Array_0_999", min=0, max=999) + _element_node_xml("Array_0_X", min=0, max="unbounded") + _element_node_xml("Array_x_2", max=2) + _element_node_xml("Array_x_999", max=999) + _element_node_xml("Array_x_X", max="unbounded") + _element_node_xml("Array_1_2", min=1, max=2) + _element_node_xml("Array_1_999", min=1, max=999) + _element_node_xml("Array_1_X", min=1, max="unbounded") + _element_node_xml("Array_5_5", min=5, max=5) + _element_node_xml("Array_5_999", min=5, max=999) + _element_node_xml("Array_5_X", min=5, max="unbounded") + """ """)) schema = client.wsdl.schema def a(schema, name, min=None, max=None): element = schema.elements[name, "my-namespace"] if min is None: assert element.min is None min = 1 else: assert str(min) == element.min if max is None: assert element.max is None max = 1 else: assert str(max) == element.max expected_optional = min == 0 assert expected_optional == element.optional() expected_required = not expected_optional assert expected_required == element.required() expected_multi_occurrence = (max == "unbounded") or (max > 1) assert expected_multi_occurrence == element.multi_occurrence() a(schema, "AnElement1") a(schema, "AnElement2", min=1) a(schema, "AnElement3", max=1) a(schema, "AnOptionalElement1", min=0) a(schema, "AnOptionalElement2", min=0, max=1) a(schema, "Array_0_2", min=0, max=2) a(schema, "Array_0_999", min=0, max=999) a(schema, "Array_0_X", min=0, max="unbounded") a(schema, "Array_x_2", max=2) a(schema, "Array_x_999", max=999) a(schema, "Array_x_X", max="unbounded") a(schema, "Array_1_2", min=1, max=2) a(schema, "Array_1_999", min=1, max=999) a(schema, "Array_1_X", min=1, max="unbounded") a(schema, "Array_5_5", min=5, max=5) a(schema, "Array_5_999", min=5, max=999) a(schema, "Array_5_X", min=5, max="unbounded") def test_schema_node_resolve(): client = testutils.client_from_wsdl(b("""\ """)) schema = client.wsdl.schema # Collect references to the test schema type nodes. assert len(schema.types) == 1 typo = schema.types["Typo", "my-namespace"] typo_u1 = typo.children()[0][0] assert typo_u1.name == "u1" # Collect references to the test schema element nodes. assert len(schema.elements) == 2 elemento = schema.elements["Elemento", "my-namespace"] elemento_x2 = elemento.children()[1][0] assert elemento_x2.name == "x2" elemento_x3 = elemento.children()[2][0] assert elemento_x3.name == "x3" elemento_typed = schema.elements["ElementoTyped", "my-namespace"] # Resolving top-level locally defined non-content nodes. assert typo.resolve() is typo # Resolving a correctly typed top-level locally typed element. assert elemento.resolve() is elemento # Resolving top-level globally typed elements. assert elemento_typed.resolve() is typo # Resolving a subnode referencing a globally defined type. assert elemento_x2.resolve() is typo # Resolving a locally defined subnode. assert elemento_x3.resolve() is elemento_x3 # Resolving builtin type nodes. assert typo_u1.resolve().__class__ is suds.xsd.sxbuiltin.XString assert typo_u1.resolve(nobuiltin=False).__class__ is \ suds.xsd.sxbuiltin.XString assert typo_u1.resolve(nobuiltin=True) is typo_u1 assert elemento_x2.resolve(nobuiltin=True) is typo assert elemento_x3.resolve(nobuiltin=True) is elemento_x3 def test_schema_node_resolve__nobuiltin_caching(): client = testutils.client_from_wsdl(b("""\ """)) schema = client.wsdl.schema # Collect references to the test schema element nodes. assert len(schema.elements) == 4 e1 = schema.elements["Elemento1", "my-namespace"] e2 = schema.elements["Elemento2", "my-namespace"] e3 = schema.elements["Elemento3", "my-namespace"] e4 = schema.elements["Elemento4", "my-namespace"] # Repeating the same resolve() call twice makes sure that the first call # does not cache an incorrect value, thus causing the second call to return # an incorrect result. assert e1.resolve().__class__ is suds.xsd.sxbuiltin.XString assert e1.resolve().__class__ is suds.xsd.sxbuiltin.XString assert e2.resolve(nobuiltin=True) is e2 assert e2.resolve(nobuiltin=True) is e2 assert e3.resolve().__class__ is suds.xsd.sxbuiltin.XString assert e3.resolve(nobuiltin=True) is e3 assert e3.resolve(nobuiltin=True) is e3 assert e4.resolve(nobuiltin=True) is e4 assert e4.resolve().__class__ is suds.xsd.sxbuiltin.XString assert e4.resolve().__class__ is suds.xsd.sxbuiltin.XString def test_schema_node_resolve__invalid_type(): client = testutils.client_from_wsdl(b("""\ """)) schema = client.wsdl.schema assert len(schema.elements) == 3 elemento1 = schema.elements["Elemento1", "my-namespace"] elemento2 = schema.elements["Elemento2", "my-namespace"] elemento3 = schema.elements["Elemento3", "my-namespace"] pytest.raises(suds.TypeNotFound, elemento1.resolve) pytest.raises(suds.TypeNotFound, elemento2.resolve) pytest.raises(suds.TypeNotFound, elemento3.resolve) def test_schema_node_resolve__references(): client = testutils.client_from_wsdl(b("""\ """)) schema = client.wsdl.schema # Collect references to the test schema element & type nodes. assert len(schema.types) == 1 typo = schema.types["Typo", "my-namespace"] assert len(schema.elements) == 10 elemento_typed = schema.elements["ElementoTyped", "my-namespace"] elemento_typed11 = schema.elements["ElementoTyped11", "my-namespace"] elemento_typed12 = schema.elements["ElementoTyped12", "my-namespace"] elemento_typed13 = schema.elements["ElementoTyped13", "my-namespace"] elemento_typed21 = schema.elements["ElementoTyped21", "my-namespace"] elemento_typed22 = schema.elements["ElementoTyped22", "my-namespace"] elemento_typed23 = schema.elements["ElementoTyped23", "my-namespace"] elemento_typedX = schema.elements["ElementoTypedX", "my-namespace"] elemento_typedX1 = schema.elements["ElementoTypedX1", "my-namespace"] elemento_typedX2 = schema.elements["ElementoTypedX2", "my-namespace"] # For referenced element node chains try resolving their nodes in both # directions and try resolving them twice to try and avoid any internal # resolve result caching that might cause some recursive resolution branch # to not get taken. # # Note that these assertions are actually redundant since inter-element # references get processed and referenced type information merged back into # the referencee when the schema information is loaded so no recursion is # needed here in the first place. The tests should still be left in place # and pass to serve as a safeguard in case this reference processing gets # changed in the future. assert elemento_typed11.resolve() is typo assert elemento_typed11.resolve() is typo assert elemento_typed13.resolve() is typo assert elemento_typed13.resolve() is typo assert elemento_typed23.resolve() is typo assert elemento_typed23.resolve() is typo assert elemento_typed21.resolve() is typo assert elemento_typed21.resolve() is typo # Recursive element references. assert elemento_typedX.resolve() is elemento_typedX assert elemento_typedX1.resolve() is elemento_typedX1 assert elemento_typedX2.resolve() is elemento_typedX2 def test_schema_object_child_access_by_index(): client = testutils.client_from_wsdl(b("""\ """)) service = client.sd[0] a_type = service.types[0][0] sequence = a_type.rawchildren[0] assert isinstance(sequence, suds.xsd.sxbasic.Sequence) children = a_type.children() assert isinstance(children, list) assert sequence[-1] is None #TODO: Children are returned as a 2-tuple containing the child element and # its ancestry (list of its parent elements). For some reason the ancestry # list is returned as a new list on every __getitem__() call and so can not # be compared using the 'is' operator. Also the children() function and # accessing children by index does not seem to return ancestry lists of the # same depth. See whether this can be updated so we always get the same # ancestry list object. #TODO: Add more detailed tests for the ancestry list structure. #TODO: Add more detailed tests for the rawchildren list structure. assert isinstance(sequence[0], tuple) assert len(sequence[0]) == 2 assert sequence[0][0] is children[0][0] assert isinstance(sequence[1], tuple) assert len(sequence[1]) == 2 assert sequence[1][0] is children[1][0] assert isinstance(sequence[2], tuple) assert len(sequence[2]) == 2 assert sequence[2][0] is children[2][0] assert sequence[3] is None def test_simple_wsdl(): client = testutils.client_from_wsdl(b("""\ """)) # Target namespace. assert client.wsdl.tns[0] == "ns" assert client.wsdl.tns[1] == "my-namespace" # Elements. assert len(client.wsdl.schema.elements) == 2 element_in = client.wsdl.schema.elements["f", "my-namespace"] element_out = client.wsdl.schema.elements["fResponse", "my-namespace"] assert isinstance(element_in, suds.xsd.sxbasic.Element) assert isinstance(element_out, suds.xsd.sxbasic.Element) assert element_in.name == "f" assert element_out.name == "fResponse" assert len(element_in.children()) == 2 param_in_1 = element_in.children()[0][0] param_in_2 = element_in.children()[1][0] assert param_in_1.name == "a" assert param_in_1.type == _string_type assert param_in_2.name == "b" assert param_in_2.type == _string_type assert len(element_out.children()) == 1 param_out_1 = element_out.children()[0][0] assert param_out_1.name == "c" assert param_out_1.type == _string_type # Service definition. assert len(client.sd) == 1 service_definition = client.sd[0] assert service_definition.wsdl is client.wsdl # Service. assert len(client.wsdl.services) == 1 service = client.wsdl.services[0] assert service_definition.service is service # Ports. assert len(service.ports) == 1 port = service.ports[0] assert len(service_definition.ports) == 1 assert len(service_definition.ports[0]) == 2 assert service_definition.ports[0][0] is port # Methods (from WSDL). assert len(port.methods) == 1 method = port.methods["f"] assert method.name == "f" assert method.location == "https://localhost/dummy" # Operations (from WSDL). assert len(client.wsdl.bindings) == 1 binding_qname, binding = _first_from_dict(client.wsdl.bindings) assert binding_qname == ("dummy", "my-namespace") assert binding.__class__ is suds.wsdl.Binding assert len(binding.operations) == 1 operation = next(itervalues(binding.operations)) input = operation.soap.input.body output = operation.soap.output.body assert len(input.parts) == 1 assert len(output.parts) == 1 input_element_qname = input.parts[0].element output_element_qname = output.parts[0].element assert input_element_qname == element_in.qname assert output_element_qname == element_out.qname # Methods (from service definition, for format specifications see the # suds.serviceDefinition.ServiceDefinition.addports() docstring). port, methods = service_definition.ports[0] assert len(methods) == 1 method_name, method_params = methods[0] assert method_name == "f" param_name, param_element, param_ancestry = method_params[0] assert param_name == "a" assert param_element is param_in_1 assert len(param_ancestry) == 3 assert type(param_ancestry[0]) is suds.xsd.sxbasic.Element assert param_ancestry[0].name == "f" assert type(param_ancestry[1]) is suds.xsd.sxbasic.Complex assert type(param_ancestry[2]) is suds.xsd.sxbasic.Sequence param_name, param_element, param_ancestry = method_params[1] assert param_name == "b" assert param_element is param_in_2 assert len(param_ancestry) == 3 assert type(param_ancestry[0]) is suds.xsd.sxbasic.Element assert param_ancestry[0].name == "f" assert type(param_ancestry[1]) is suds.xsd.sxbasic.Complex assert type(param_ancestry[2]) is suds.xsd.sxbasic.Sequence def test_wsdl_schema_content(): client = testutils.client_from_wsdl(b("""\ """)) # Elements. assert len(client.wsdl.schema.elements) == 1 elemento = client.wsdl.schema.elements["Elemento", "my-namespace"] assert isinstance(elemento, suds.xsd.sxbasic.Element) pytest.raises(KeyError, client.wsdl.schema.elements.__getitem__, ("DoesNotExist", "OMG")) # Types. assert len(client.wsdl.schema.types) == 2 unga_bunga = client.wsdl.schema.types["UngaBunga", "my-namespace"] assert isinstance(unga_bunga, suds.xsd.sxbasic.Complex) fifi = client.wsdl.schema.types["Fifi", "my-namespace"] assert isinstance(unga_bunga, suds.xsd.sxbasic.Complex) pytest.raises(KeyError, client.wsdl.schema.types.__getitem__, ("DoesNotExist", "OMG")) def _assert_dynamic_type(anObject, typename): assert anObject.__module__ == suds.sudsobject.__name__ assert anObject.__metadata__.sxtype.name == typename # In order to be compatible with old style classes (py2 only) we need to # access the object's class information using its __class__ member and not # the type() function. type() function always returns for # old-style class instances while the __class__ member returns the correct # class information for both old and new-style classes. assert anObject.__class__.__module__ == suds.sudsobject.__name__ assert anObject.__class__.__name__ == typename def _construct_SOAP_request(client, operation_name, *args, **kwargs): """ Construct a SOAP request for a given web service operation invocation. To make the test case code calling this function simpler, assumes we want to call the operation on the given client's first service & port. """ method = client.wsdl.services[0].ports[0].methods[operation_name] return method.binding.input.get_message(method, args, kwargs) def _element_node_xml(name, min=None, max=None): s = [] s.append(' \n") return "".join(s) def _first_from_dict(d): """Returns the first name/value pair from a dictionary or None if empty.""" for x in d.items(): return x[0], x[1] _string_type = ("string", "http://www.w3.org/2001/XMLSchema") suds-1.1.2/tests/test_timezone.py000066400000000000000000000052651425611400200171010ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Unit tests for Timezone modeling classes implemented in suds. Implemented using the 'pytest' testing framework. """ if __name__ == "__main__": import testutils testutils.run_using_pytest(globals()) from suds.sax.date import FixedOffsetTimezone, UtcTimezone import pytest import datetime class TestFixedOffsetTimezone: """Tests for the suds.sax.date.FixedOffsetTimezone class.""" @pytest.mark.parametrize(("h", "m", "name"), ( (-13, 0, "-13:00"), (-5, 0, "-05:00"), (0, 0, "+00:00"), (5, 0, "+05:00"), (13, 0, "+13:00"), (5, 50, "+05:50"), (-4, 31, "-04:31"))) def test(self, h, m, name): tz_delta = datetime.timedelta(hours=h, minutes=m) tz = FixedOffsetTimezone(tz_delta) assert tz.utcoffset(None) is tz_delta assert tz.dst(None) == datetime.timedelta(0) assert tz.tzname(None) == name assert str(tz) == "FixedOffsetTimezone " + name @pytest.mark.parametrize(("h", "m", "s", "us"), ( (-22, 10, 1, 0), (-5, 0, 59, 0), (0, 0, 0, 1), (12, 12, 0, 120120), (12, 12, 0, 999999))) def testTooPreciseOffset(self, h, m, s, us): o = datetime.timedelta(hours=h, minutes=m, seconds=s, microseconds=us) pytest.raises(ValueError, FixedOffsetTimezone, o) @pytest.mark.parametrize("hours", (-5, 0, 5)) def testConstructFromInteger(self, hours): tz = FixedOffsetTimezone(hours) assert tz.utcoffset(None) == datetime.timedelta(hours=hours) class TestUtcTimezone: """Tests for the suds.sax.date.UtcTimezone class.""" def test(self): tz = UtcTimezone() assert tz.utcoffset(None) == datetime.timedelta(0) assert tz.dst(None) == datetime.timedelta(0) assert tz.tzname(None) == "UTC" assert str(tz) == "UtcTimezone" suds-1.1.2/tests/test_transport.py000066400000000000000000000200611425611400200172720ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Suds library transport related unit tests. Implemented using the 'pytest' testing framework. """ if __name__ == "__main__": import testutils testutils.run_using_pytest(globals()) import suds from suds.transport import Reply, Request, Transport import suds.transport.options import pytest from six import b, text_type, u, unichr import sys class TestBaseTransportClass: def test_members(self): t = Transport() assert t.options.__class__ is suds.transport.options.Options @pytest.mark.parametrize("method_name", ("open", "send")) def test_methods_should_be_abstract(self, monkeypatch, method_name): monkeypatch.delitem(locals(), "e", False) transport = Transport() f = getattr(transport, method_name) e = pytest.raises(Exception, f, "whatever").value try: assert e.__class__ is Exception assert str(e) == "not-implemented" finally: del e # explicitly break circular reference chain in Python 3 class TestReply: @pytest.mark.parametrize(("code", "headers", "message"), ( (1, {}, None), (1, {}, u("ola")), (1, {}, b("ola")), (1, {}, object()), (1, {}, u("\u0161u\u0107-mu\u0107 \u4E2D\u539F\u5343\n\u57CE")), (2, {"semper": "fi"}, u("\u4E2D\u539F\u5343\n\u57CE")))) def test_construction(self, code, headers, message): reply = Reply(code, headers, message) assert reply.code is code assert reply.headers is headers assert reply.message is message @pytest.mark.parametrize("message", [u(x).encode("utf-8") for x in ( "", "for a bitch it's haaaard...", """\ I'm here to kick ass, and chew bubble gum... and I'm all out of gum.""", "\u0161u\u0107-mu\u0107 pa o\u017Ee\u017Ei.. za 100 \u20AC\n\n" "with multiple\nlines...", "\n\n\n\n\n\n", "\u4E2D\u539F\u5343\u519B\u9010\u848B")]) def test_string_representation(self, message): code = 17 reply = Reply(code, {"aaa": 1}, message) expected = u("""\ CODE: %s HEADERS: %s MESSAGE: %s""") % (code, reply.headers, message.decode("raw_unicode_escape")) assert text_type(reply) == expected if sys.version_info < (3,): assert str(reply) == expected.encode("utf-8") class TestRequest: @pytest.mark.parametrize("message", ( None, "it's hard out here...", u("\u57CE\u697C\u4E07\u4F17\u68C0\u9605"))) def test_construct(self, message): # Always use the same URL as different ways to specify a Request's URL # are tested separately. url = "some://url" timeout = 10 request = Request(url, message, timeout) assert request.url is url assert request.message is message assert request.headers == {} assert request.timeout == timeout def test_construct_with_no_message(self): request = Request("some://url") assert request.headers == {} assert request.message is None test_non_ASCII_URLs = [ u("\u4E2D\u539F\u5343\u519B\u9010\u848B"), u("\u57CE\u697C\u4E07\u4F17\u68C0\u9605")] + [ url_prefix + url_suffix for url_prefix in (u(""), u("Jurko")) for url_suffix in (unichr(128), unichr(200), unichr(1000))] @pytest.mark.parametrize("url", test_non_ASCII_URLs + # unicode strings [x.encode("utf-8") for x in test_non_ASCII_URLs]) # byte strings def test_non_ASCII_URL(self, url): """Transport Request should reject URLs with non-ASCII characters.""" pytest.raises(UnicodeError, Request, url) @pytest.mark.parametrize(("url", "headers", "message"), ( ("my URL", {}, ""), ("", {"aaa": "uf-uf"}, "for a bitch it's haaaard..."), ("http://rumple-fif/muka-laka-hiki", {"uno": "eins", "zwei": "due"}, """\ I'm here to kick ass, and chew bubble gum... and I'm all out of gum."""), ("", {}, u("\u0161u\u0107-mu\u0107 pa o\u017Ee\u017Ei.. za 100 " "\u20AC\n\nwith multiple\nlines...")), ("", {}, "\n\n\n\n\n\n"), ("", {}, u("\u4E2D\u539F\u5343\u519B\u9010\u848B")))) def test_string_representation_with_message(self, url, headers, message): for key, value in list(headers.items()): old_key = key if isinstance(key, text_type): key = key.encode("utf-8") del headers[old_key] if isinstance(value, text_type): value = value.encode("utf-8") headers[key] = value if isinstance(message, text_type): message = message.encode("utf-8") request = Request(url, message) request.headers = headers expected = u("""\ URL: %s HEADERS: %s MESSAGE: %s""") % (url, request.headers, message.decode("raw_unicode_escape")) assert text_type(request) == expected if sys.version_info < (3,): assert str(request) == expected.encode("utf-8") def test_string_representation_with_no_message(self): url = "look at my silly little URL" headers = {suds.byte_str("yuck"): suds.byte_str("ptooiii...")} request = Request(url) request.headers = headers expected = u("""\ URL: %s HEADERS: %s""") % (url, request.headers) assert text_type(request) == expected if sys.version_info < (3,): assert str(request) == expected.encode("utf-8") test_URLs = [ u(""), u("http://host/path/name"), u("cogito://ergo/sum"), u("haleluya"), u("look at me flyyyyyyyy"), unichr(127), u("Jurko") + unichr(127)] @pytest.mark.parametrize("url", test_URLs + [ url.encode("ascii") for url in test_URLs]) def test_URL(self, url): """ Transport Request accepts its URL as either a byte or a unicode string. Internally URL information is kept as the native Python str type. """ request = Request(url) assert isinstance(request.url, str) if url.__class__ is str: assert request.url is url elif url.__class__ is u: assert request.url == url.encode("ascii") # Python 2. else: assert request.url == url.decode("ascii") # Python 3. test_URLs = [unichr(0), u("Jurko") + unichr(0)] if sys.version_info <= (3, 6) else [] # "https://bugs.python.org/issue32745" @pytest.mark.parametrize("url", test_URLs + [ url.encode("ascii") for url in test_URLs]) def test_URL_null_bytes(self, url): """ Transport Request accepts its URL as either a byte or a unicode string. Internally URL information is kept as the native Python str type. """ request = Request(url) assert isinstance(request.url, str) if url.__class__ is str: assert request.url is url elif url.__class__ is u: assert request.url == url.encode("ascii") # Python 2. else: assert request.url == url.decode("ascii") # Python 3. suds-1.1.2/tests/test_transport_http.py000066400000000000000000000625431425611400200203440ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Suds library HTTP transport related unit tests. Implemented using the 'pytest' testing framework. """ import testutils if __name__ == "__main__": testutils.run_using_pytest(globals()) import suds import suds.transport import suds.transport.http import pytest from six import u from six.moves import http_client from six.moves.urllib.error import HTTPError from six.moves.urllib.request import ProxyHandler import base64 import re import sys from email.message import Message # We can not use six.moves modules for this since we want to monkey-patch the # exact underlying urllib2/urllib.request module in our tests and not just # their six.moves proxy module. if sys.version_info < (3,): import urllib2 as urllib_request else: import urllib.request as urllib_request class MustNotBeCalled(Exception): """Local exception used in this test module.""" pass class MyException(Exception): """Local exception used in this test module.""" pass class Undefined: """Internal tag class indicating undefined function call parameters.""" pass class CountedMock(object): """ Base mock object class supporting generic attribute access counting. Ignores attributes whose name starts with 'mock_' or '__mock_' or their transformed variant '___mock_'. Derived classes must call this class's __init__() or mock_reset() methods during their initialization, but both calls are not needed. Before this initialization, all counters will be reported as None. """ def __init__(self): self.mock_reset() def __getattribute__(self, name): get = super(CountedMock, self).__getattribute__ counter_name = "_CountedMock__mock_call_counter" has_counter = False try: counter = get(counter_name) has_counter = True except AttributeError: pass if has_counter: if name == counter_name: return counter if not re.match("(_.+__)?mock_", name): counter[name] = counter.get(name, 0) + 1 return get(name) def mock_call_count(self, name): if self.__mock_call_counter: return self.__mock_call_counter.get(name, 0) def mock_reset(self): self.__mock_call_counter = {} class MockFP: """ Mock FP 'File object' as stored inside Python's HTTPError exception. Must have several 'File object' methods defined on it as Python's HTTPError implementation expects them and stores references to them internally, at least with Python 2.4. """ def read(): raise MustNotBeCalled def readline(): raise MustNotBeCalled def close(self): pass class MockURLOpenerSaboteur: """ Mock URLOpener raising an exception in its open() method. If no open_exception is given in its initializer, simply marks the current test as a failure if its open() method is called. Otherwise raises the given exception from that call. """ def __init__(self, open_exception=None): self.__open_exception = open_exception self.args = None def open(self, *args, **kwargs): self.args = args if self.__open_exception: raise self.__open_exception pytest.fail("urllib urlopener.open() must not be called.") class SendMethodFixture: """ Instances of this class get returned by the send_method test fixture. Each instance is connected to a specific suds.transport.http.HttpTransport request sending method and may be used to call that method on a specific suds.transport.http.HttpTransport instance. """ def __init__(self, name): self.name = name def __call__(self, transport, *args, **kwargs): assert isinstance(transport, suds.transport.http.HttpTransport) return getattr(transport, self.name)(*args, **kwargs) # Test URL data used by several tests in this test module. test_URL_data = ( "sudo://make-me-a-sammich", "http://my little URL", "https://my little URL", "xxx://my little URL", "xxx:my little URL", "xxx:") def assert_default_transport(transport): """Test utility verifying default constructed transport content.""" assert isinstance(transport, suds.transport.http.HttpTransport) assert transport.urlopener is None def create_request(url="protocol://default-url", data=u("Rumpelstiltskin")): """Test utility constructing a suds.transport.Request instance.""" return suds.transport.Request(url, data) @pytest.fixture(params=["open", "send"]) def send_method(request): """ pytest testing framework based test fixture causing tests using that fixture to be called for each suds.transport.http.HttpTransport request sending method. """ return SendMethodFixture(request.param) @pytest.mark.parametrize("input", ( dict(), dict(password="Humpty"), dict(username="Dumpty"), dict(username="Habul Afufa", password="preCious"), # Regression test: Extremely long username & password combinations must not # cause suds to add additional newlines in the constructed 'Authorization' # HTTP header. dict(username="An Extremely Long Username that could be usable only to " "Extremely Important People whilst on Extremely Important Missions.", password="An Extremely Long Password that could be usable only to " "Extremely Important People whilst on Extremely Important Missions. " "And some extra 'funny' characters to improve security: " "!@#$%^&*():|}|{{.\nEven\nSome\nNewLines\n" " and spaces at the start of a new line. "))) def test_authenticated_http_add_credentials_to_request(input): class MockRequest: def __init__(self): self.headers = {} def assert_Authorization_header(request, username, password): if username is None or password is None: assert len(request.headers) == 0 else: assert len(request.headers) == 1 header = request.headers["Authorization"] assert header == _encode_basic_credentials(username, password) username = input.get("username", None) password = input.get("password", None) t = suds.transport.http.HttpAuthenticated(**input) r = MockRequest() t.addcredentials(r) assert_Authorization_header(r, username, password) @pytest.mark.parametrize("input", ( dict(password="riff raff..!@#"), dict(username="macro"), dict(username="Hab AfuFa", password="preCious"))) def test_construct_authenticated_http(input): expected_username = input.get("username", None) expected_password = input.get("password", None) transport = suds.transport.http.HttpAuthenticated(**input) assert transport.credentials() == (expected_username, expected_password) assert_default_transport(transport) def test_request_headers_are_passed_to_urllib_when_opening_a_request(): class MockRequest(object): url = "http://somewhere.far" headers = {'Key': 'value'} mock_request = MockRequest() transport = suds.transport.http.HttpTransport() transport.urlopener = MockURLOpenerSaboteur(MyException) pytest.raises(MyException, suds.transport.http.HttpTransport.open, transport, mock_request) assert transport.urlopener.args[0].headers == mock_request.headers def test_construct_http(): transport = suds.transport.http.HttpTransport() assert_default_transport(transport) def test_sending_using_network_sockets(send_method, monkeypatch): """ Test that telling HttpTransport to send a request actually causes it to send the expected data over the network. In order to test this we need to make suds attempt to send a HTTP request over the network. On the other hand, we want the test to work even on computers not connected to a network so we monkey-patch the underlying network socket APIs, log all the data suds attempts to send over the network and consider the test run successful once suds attempts to read back data from the network. """ class Mocker(CountedMock): def __init__(self, expected_host, expected_port): self.expected_host = expected_host self.expected_port = expected_port self.host_address = object() self.mock_reset() def getaddrinfo(self, host, port, *args, **kwargs): assert host == self.expected_host assert port == self.expected_port return [(None, None, None, None, self.host_address)] def mock_reset(self): super(Mocker, self).mock_reset() self.mock_sent_data = suds.byte_str() self.mock_socket = None def socket(self, *args, **kwargs): assert self.mock_socket is None self.mock_socket = MockSocket(self) return self.mock_socket class MockSocket(CountedMock): def __init__(self, mocker): self.__mocker = mocker self.mock_reset() def close(self): pass def connect(self, address): assert address is self.__mocker.host_address def makefile(self, *args, **kwargs): assert self.mock_reader is None self.mock_reader = MockSocketReader() return self.mock_reader def mock_reset(self): super(MockSocket, self).mock_reset() self.mock_reader = None def sendall(self, data): self.__mocker.mock_sent_data += data def settimeout(self, *args, **kwargs): pass def setsockopt(self, *args, **kwargs): pass class MockSocketReader(CountedMock): def __init__(self): super(MockSocketReader, self).__init__() def readline(self, *args, **kwargs): raise MyException def close(self): pass def flush(self): pass # Setup. host = "an-easily-recognizable-host-name-214894932" port = 9999 host_port = "%s:%s" % (host, port) url_relative = "svc" url = "http://%s/%s" % (host_port, url_relative) partial_ascii_byte_data = suds.byte_str("Muka-laka-hiki") non_ascii_byte_data = u("\u0414\u043C\u0438 \u0442\u0440").encode("utf-8") non_ascii_byte_data += partial_ascii_byte_data mocker = Mocker(host, port) monkeypatch.setattr("socket.getaddrinfo", mocker.getaddrinfo) monkeypatch.setattr("socket.socket", mocker.socket) request = suds.transport.Request(url, non_ascii_byte_data) transport = suds.transport.http.HttpTransport() expected_sent_data_start, expected_request_data_send = { "open": ("GET", False), "send": ("POST", True)}[send_method.name] # Execute. pytest.raises(MyException, send_method, transport, request) # Verify. assert mocker.mock_call_count("getaddrinfo") == 1 assert mocker.mock_call_count("socket") == 1 assert mocker.mock_socket.mock_call_count("connect") == 1 assert mocker.mock_socket.mock_call_count("makefile") == 1 # With older Python versions, e.g. Python 2.4, urllib implementation calls # Socket's sendall() method twice - once for sending the HTTP request # headers and once for its body. assert mocker.mock_socket.mock_call_count("sendall") in (1, 2) # Python versions prior to 3.4.2 do not explicitly close their HTTP server # connection socket in case of our custom exceptions, e.g. version 3.4.1. # closes it only on OSError exceptions. assert mocker.mock_socket.mock_call_count("close") in (0, 1) # With older Python versions, e.g. Python 2.4, Socket class does not # implement the settimeout() method. assert mocker.mock_socket.mock_call_count("settimeout") in (0, 1) assert mocker.mock_socket.mock_reader.mock_call_count("readline") == 1 assert mocker.mock_sent_data.__class__ is suds.byte_str_class expected_sent_data_start = "%s /%s HTTP/1.1\r\n" % ( expected_sent_data_start, url_relative) expected_sent_data_start = suds.byte_str(expected_sent_data_start) assert mocker.mock_sent_data.startswith(expected_sent_data_start) assert host_port.encode("utf-8") in mocker.mock_sent_data if expected_request_data_send: assert mocker.mock_sent_data.endswith(non_ascii_byte_data) else: assert partial_ascii_byte_data not in mocker.mock_sent_data class TestSendingToURLWithAMissingProtocolIdentifier: """ Test suds reporting URLs with a missing protocol identifier. Python urllib library makes this check under Python 3.x, but not under earlier Python versions. """ # We can not set this 'url' fixture data using a class decorator since that # Python feature has been introduced in Python 2.6 and we need to keep this # code backward compatible with Python 2.4. invalid_URL_parametrization = pytest.mark.parametrize("url", ( "my no-protocol URL", ":my no-protocol URL")) @pytest.mark.skipif(sys.version_info >= (3,), reason="Python 2 specific") @invalid_URL_parametrization def test_python2(self, url, send_method): transport = suds.transport.http.HttpTransport() transport.urlopener = MockURLOpenerSaboteur(MyException) request = create_request(url) pytest.raises(MyException, send_method, transport, request) @pytest.mark.skipif(sys.version_info < (3,), reason="Python 3+ specific") @invalid_URL_parametrization def test_python3(self, url, send_method, monkeypatch): monkeypatch.delitem(locals(), "e", False) transport = suds.transport.http.HttpTransport() transport.urlopener = MockURLOpenerSaboteur() request = create_request(url) e = pytest.raises(ValueError, send_method, transport, request) try: assert "unknown url type" in str(e.value) finally: del e # explicitly break circular reference chain in Python 3 class TestURLOpenerUsage: """ Test demonstrating how suds.transport.http.HttpTransport makes use of the urllib library to perform the actual network transfers. The main contact point with the urllib library are its OpenerDirector objects we refer to as 'urlopener'. """ @staticmethod def create_HTTPError(url=Undefined, code=Undefined, msg=Undefined, hdrs=Undefined, fp=None): """ Test utility method constructing a HTTPError instance. Allows callers to construct a HTTPError instance using input data they are interested in, with some built-in default values used for any input data they are not interested in. """ if url is Undefined: url = object() if code is Undefined: code = object() if msg is Undefined: msg = object() if hdrs is Undefined: hdrs = object() return HTTPError(url=url, code=code, msg=msg, hdrs=hdrs, fp=fp) @pytest.mark.parametrize("status_code", ( http_client.ACCEPTED, http_client.NO_CONTENT, http_client.RESET_CONTENT, http_client.MOVED_PERMANENTLY, http_client.BAD_REQUEST, http_client.PAYMENT_REQUIRED, http_client.FORBIDDEN, http_client.NOT_FOUND, http_client.INTERNAL_SERVER_ERROR, http_client.NOT_IMPLEMENTED, http_client.HTTP_VERSION_NOT_SUPPORTED)) def test_open_propagating_HTTPError_exceptions(self, status_code, monkeypatch): """ HttpTransport open() operation should transform HTTPError urlopener exceptions to suds.transport.TransportError exceptions. """ # Setup. monkeypatch.delattr(locals(), "e", False) fp = MockFP() e_original = self.create_HTTPError(code=status_code, fp=fp) t = suds.transport.http.HttpTransport() t.urlopener = MockURLOpenerSaboteur(open_exception=e_original) request = create_request() # Execute. e = pytest.raises(suds.transport.TransportError, t.open, request).value try: # Verify. assert e.args == (str(e_original),) assert e.httpcode is status_code assert e.fp is fp finally: del e # explicitly break circular reference chain in Python 3 @pytest.mark.xfail(reason="original suds library bug") @pytest.mark.parametrize("status_code", ( http_client.ACCEPTED, http_client.NO_CONTENT)) def test_operation_invoke_with_urlopen_accept_no_content__data(self, status_code): """ suds.client.Client web service operation invocation expecting output data, and for which a corresponding urlopen call raises a HTTPError with status code ACCEPTED or NO_CONTENT, should report this as a TransportError. """ e = self.create_HTTPError(code=status_code) transport = suds.transport.http.HttpTransport() transport.urlopener = MockURLOpenerSaboteur(open_exception=e) wsdl = testutils.wsdl('', output="o", operation_name="f") client = testutils.client_from_wsdl(wsdl, transport=transport) pytest.raises(suds.transport.TransportError, client.service.f) @pytest.mark.xfail(reason="original suds library bug") @pytest.mark.parametrize("status_code", ( http_client.ACCEPTED, http_client.NO_CONTENT)) def test_operation_invoke_with_urlopen_accept_no_content__no_data(self, status_code): """ suds.client.Client web service operation invocation expecting no output data, and for which a corresponding urlopen call raises a HTTPError with status code ACCEPTED or NO_CONTENT, should treat this as a successful invocation. """ # We are not yet sure that the behaviour checked for in this test is # actually desired. The test is only an 'educated guess' prepared to # demonstrate a related problem in the original suds library # implementation. The original implementation is definitely buggy as # its web service operation invocation raises an AttributeError # exception by attempting to access a non-existing 'None.message' # attribute internally. e = self.create_HTTPError(code=status_code) transport = suds.transport.http.HttpTransport() transport.urlopener = MockURLOpenerSaboteur(open_exception=e) wsdl = testutils.wsdl('', output="o", operation_name="f") client = testutils.client_from_wsdl(wsdl, transport=transport) assert client.service.f() is None def test_propagating_non_HTTPError_exceptions(self, send_method): """ HttpTransport data sending operations need to propagate non-HTTPError exceptions raised by the underlying urlopen call. """ e = MyException() t = suds.transport.http.HttpTransport() t.urlopener = MockURLOpenerSaboteur(open_exception=e) assert pytest.raises(e.__class__, t.open, create_request()).value is e @pytest.mark.parametrize("status_code", ( http_client.RESET_CONTENT, http_client.MOVED_PERMANENTLY, http_client.BAD_REQUEST, http_client.PAYMENT_REQUIRED, http_client.FORBIDDEN, http_client.NOT_FOUND, http_client.INTERNAL_SERVER_ERROR, http_client.NOT_IMPLEMENTED, http_client.HTTP_VERSION_NOT_SUPPORTED)) def test_send_transforming_HTTPError_exceptions(self, status_code, monkeypatch): """ HttpTransport send() operation should transform HTTPError urlopener exceptions with status codes other than ACCEPTED or NO_CONTENT to suds.transport.TransportError exceptions. """ # Setup. monkeypatch.delattr(locals(), "e", False) msg = object() fp = MockFP() e_original = self.create_HTTPError(msg=msg, code=status_code, fp=fp) t = suds.transport.http.HttpTransport() t.urlopener = MockURLOpenerSaboteur(open_exception=e_original) request = create_request() # Execute. e = pytest.raises(suds.transport.TransportError, t.send, request).value try: # Verify. assert len(e.args) == 1 assert e.args[0] is e_original.msg assert e.httpcode is status_code assert e.fp is fp finally: del e # explicitly break circular reference chain in Python 3 @pytest.mark.parametrize("status_code", ( http_client.ACCEPTED, http_client.NO_CONTENT)) def test_send_transforming_HTTPError_exceptions__accepted_no_content(self, status_code): """ HttpTransport send() operation should return None when their underlying urlopen operation raises a HTTPError exception with status code ACCEPTED or NO_CONTENT. """ e_original = self.create_HTTPError(code=status_code) t = suds.transport.http.HttpTransport() t.urlopener = MockURLOpenerSaboteur(open_exception=e_original) assert t.send(create_request()) is None def test_specify_timeout(self): """ HttpTransport send() operation should pass a Request timeout parameter to urllib """ t = suds.transport.http.HttpTransport() request = create_request() request.timeout = 10 # Python 2 compatible object class CompatibleHeaders(dict): dict = {} class MockResponse: def info(self): message = Message() # Python 2 compatible response message.getheaders = lambda k: {} return message @property def headers(self): return CompatibleHeaders() def read(self): return '' class MockURLOpener: def open(self, urllib_request, timeout=None): assert timeout == request.timeout return MockResponse() t.urlopener = MockURLOpener() t.send(request) @pytest.mark.parametrize("url", test_URL_data) def test_urlopener_default(self, url, send_method, monkeypatch): """ HttpTransport builds a new urlopener if not given an external one. """ def my_build_urlopener(*handlers): assert len(handlers) == 1 assert handlers[0].__class__ is ProxyHandler raise MyException monkeypatch.setattr(urllib_request, "build_opener", my_build_urlopener) transport = suds.transport.http.HttpTransport() request = create_request(url=url) pytest.raises(MyException, send_method, transport, request) @pytest.mark.parametrize("url", test_URL_data) def test_urlopener_indirection(self, url, send_method, monkeypatch): """ HttpTransport may be configured with an external urlopener. In that case, when opening or sending a HTTP request, a new urlopener is not built and the given urlopener is used as-is, without adding any extra handlers to it. """ class MockURLOpener: def open(self, request, timeout=None): assert request.__class__ is urllib_request.Request assert request.get_full_url() == url raise MyException transport = suds.transport.http.HttpTransport() transport.urlopener = MockURLOpener() def my_build_urlopener(*args, **kwargs): pytest.fail("urllib build_opener() called when not expected.") monkeypatch.setattr(urllib_request, "build_opener", my_build_urlopener) request = create_request(url=url) pytest.raises(MyException, send_method, transport, request) def _encode_basic_credentials(username, password): """ Encode user credentials as used in basic HTTP authentication. This is the value expected to be added to the 'Authorization' HTTP header. """ data = suds.byte_str("%s:%s" % (username, password)) return "Basic %s" % base64.b64encode(data).decode("utf-8") suds-1.1.2/tests/test_wsse.py000066400000000000000000000023171425611400200162230ustar00rootroot00000000000000import pytest from six import b, itervalues, next import testutils if __name__ == "__main__": testutils.run_using_pytest(globals()) from testutils.compare_sax import CompareSAX from suds.wsse import Security, UsernameToken class TestUsernameToken: def test_wsse_username_token(self): security = Security() token = UsernameToken("username", "password") security.tokens.append(token) expected = """ username password """ assert expected == str(security.xml()) def test_wsse_username_nonce(self): security = Security() token = UsernameToken("username", "password") token.setnonce() token.setcreated() token.setnonceencoding(True) token.setpassworddigest("digest") security.tokens.append(token) assert "= (2, 6): import fractions if sys.version_info >= (3,): long = int class _Dummy: """Class for testing unknown object class handling.""" pass # Define mock MockXType classes (e.g. MockXDate, MockXInteger & MockXString) # used to test translate() methods in different XSD data type model classes # such as XDate, XInteger & XString. def _def_mock_xsd_class(x_class_name): """ Define a mock XType class and reference it globally as MockXType. XType classes (e.g. XDate, XInteger & XString), represent built-in XSD types. Their mock counterparts created here (e.g. MockXDate, MockXInteger & MockXString) may be used to test their translate() methods without having to connect them to an actual XSD schema. This is achieved by having their constructor call take no parameters and not call the parent class __init__() method. Rationale why these mock classes are used instead of actual XType classes: * XType instances need to be connected to an actual XSD schema element which would unnecessarily complicate our test code. * XType translate() implementations are not affected by whether the instance they have been called on has been connected to an actual XSD schema element. * XType translate() functions can not be called as unbound methods, e.g. XDate.translate(...). Such an implementation would fail if those methods are not defined exactly in the specified XType class but in one of its parent classes. """ x_class = getattr(suds.xsd.sxbuiltin, x_class_name) assert issubclass(x_class, XBuiltin) mock_class_name = "Mock" + x_class_name mock_class = type(mock_class_name, (x_class,), { "__doc__": "Mock %s not connected to an XSD schema." % (x_class_name,), "__init__": lambda self: None}) globals()[mock_class_name] = mock_class for x in ("XAny", "XBoolean", "XDate", "XDateTime", "XDecimal", "XFloat", "XInteger", "XLong", "XString", "XTime"): _def_mock_xsd_class(x) # Built-in XSD data types as defined in 'XML Schema Part 2: Datatypes Second # Edition' (http://www.w3.org/TR/2004/REC-xmlschema-2-20041028). Each is paired # with its respective suds library XSD type modeling class. builtins = { "anySimpleType": XString, "anyType": XAny, "anyURI": XString, "base64Binary": XString, "boolean": XBoolean, "byte": XInteger, "date": XDate, "dateTime": XDateTime, "decimal": XDecimal, "double": XFloat, "duration": XString, "ENTITIES": XString, "ENTITY": XString, "float": XFloat, "gDay": XString, "gMonth": XString, "gMonthDay": XString, "gYear": XString, "gYearMonth": XString, "hexBinary": XString, "ID": XString, "IDREF": XString, "IDREFS": XString, "int": XInteger, "integer": XInteger, "language": XString, "long": XLong, "Name": XString, "NCName": XString, "negativeInteger": XInteger, "NMTOKEN": XString, "NMTOKENS": XString, "nonNegativeInteger": XInteger, "nonPositiveInteger": XInteger, "normalizedString": XString, "NOTATION": XString, "positiveInteger": XInteger, "QName": XString, "short": XInteger, "string": XString, "time": XTime, "token": XString, "unsignedByte": XInteger, "unsignedInt": XInteger, "unsignedLong": XLong, "unsignedShort": XInteger} # XML namespaces where all the built-in type names live, as defined in 'XML # Schema Part 2: Datatypes Second Edition' # (http://www.w3.org/TR/2004/REC-xmlschema-2-20041028). builtin_namespaces = [ "http://www.w3.org/2001/XMLSchema", "http://www.w3.org/2001/XMLSchema-datatypes"] class TestXBoolean: """suds.xsd.sxbuiltin.XBoolean.translate() tests.""" @pytest.mark.parametrize(("source", "expected"), ( (0, "false"), (1, "true"), (False, "false"), (True, "true"))) def test_from_python_object(self, source, expected): translated = MockXBoolean().translate(source, topython=False) assert translated.__class__ is str assert translated == expected @pytest.mark.parametrize("source", ( None, pytest.mark.skipif(sys.version_info >= (3,), reason="int == long since Python 3.0")(long(0)), pytest.mark.skipif(sys.version_info >= (3,), reason="int == long since Python 3.0")(long(1)), "x", "True", "False", object(), _Dummy(), datetime.date(2101, 1, 1))) def test_from_python_object__invalid(self, source): assert MockXBoolean().translate(source, topython=False) is source @pytest.mark.parametrize("source", (-1, 2, 5, 100)) def test_from_python_object__invalid_integer(self, source): #TODO: See if this special integer handling is really desired. assert MockXBoolean().translate(source, topython=False) is None @pytest.mark.parametrize(("source", "expected"), ( ("0", False), ("1", True), ("false", False), ("true", True))) def test_to_python_object(self, source, expected): assert MockXBoolean().translate(source) is expected @pytest.mark.parametrize("source", (0, 1, "", "True", "False", "2", "Z", "-1", "00", "x", "poppycock")) def test_to_python_object__invalid(self, source): assert MockXBoolean().translate(source) is None class TestXDate: """ suds.xsd.sxbuiltin.XDate.translate() tests. Related Python object <--> string conversion details are tested in a separate date/time related test module. These tests are only concerned with basic translate() functionality. """ def test_from_python_object__date(self): date = datetime.date(2013, 7, 24) translated = MockXDate().translate(date, topython=False) assert translated.__class__ is suds.sax.date.Date assert str(translated) == "2013-07-24" def test_from_python_object__datetime(self): dt = datetime.datetime(2013, 7, 24, 11, 59, 4) translated = MockXDate().translate(dt, topython=False) assert translated.__class__ is suds.sax.date.Date assert str(translated) == "2013-07-24" @pytest.mark.parametrize("source", ( None, object(), _Dummy(), datetime.time())) def test_from_python_object__invalid(self, source): assert MockXDate().translate(source, topython=False) is source def test_to_python_object(self): assert MockXDate().translate("1941-12-7") == datetime.date(1941, 12, 7) def test_to_python_object__empty_string(self): assert MockXDate().translate("") == None class TestXDateTime: """ suds.xsd.sxbuiltin.XDateTime.translate() tests. Related Python object <--> string conversion details are tested in a separate date/time related test module. These tests are only concerned with basic translate() functionality. """ def test_from_python_object(self): dt = datetime.datetime(2021, 12, 31, 11, 25) translated = MockXDateTime().translate(dt, topython=False) assert translated.__class__ is suds.sax.date.DateTime assert str(translated) == "2021-12-31T11:25:00" @pytest.mark.parametrize("source", ( None, object(), _Dummy(), datetime.time(22, 47, 9, 981), datetime.date(2101, 1, 1))) def test_from_python_object__invalid(self, source): assert MockXDateTime().translate(source, topython=False) is source def test_to_python_object(self): dt = datetime.datetime(1941, 12, 7, 10, 30, 22, 454000) assert MockXDateTime().translate("1941-12-7T10:30:22.454") == dt def test_to_python_object__empty_string(self): assert MockXDateTime().translate("") == None class TestXDecimal: """suds.xsd.sxbuiltin.XDecimal.translate() tests.""" @pytest.mark.parametrize(("source", "expected"), ( # Zeros. (decimal.Decimal("0"), "0"), (decimal.Decimal("-0"), "-0"), # Positive integral. (decimal.Decimal("1"), "1"), (decimal.Decimal("1000"), "1000"), (decimal.Decimal("1E500"), "1" + "0" * 500), # Negative integral. (decimal.Decimal("-1"), "-1"), (decimal.Decimal("-1000"), "-1000"), (decimal.Decimal("-1E500"), "-1" + "0" * 500), # Simple fractional. (decimal.Decimal("0.1"), "0.1"), (decimal.Decimal("-0.1"), "-0.1"), (decimal.Decimal("0." + "0123456789" * 9), "0." + "0123456789" * 9), (decimal.Decimal("-0." + "0123456789" * 9), "-0." + "0123456789" * 9), # Zero with extra fractional digits. (decimal.Decimal("0.0000"), "0"), (decimal.Decimal("-0.0000"), "-0"), # Only 0s as fractional digits. (decimal.Decimal("5.000000000000000000"), "5"), (decimal.Decimal("-5.000000000000000000"), "-5"), # Trailing fractional 0 digits. (decimal.Decimal("5.000000123000000000000"), "5.000000123"), (decimal.Decimal("-5.000000123000000000000"), "-5.000000123"), # Very small fractional part. (decimal.Decimal("9E-100"), "0." + "0" * 99 + "9"), (decimal.Decimal("-9E-100"), "-0." + "0" * 99 + "9"))) def test_decimal_to_xsd_value_representation(self, source, expected): assert source.__class__ is decimal.Decimal string = MockXDecimal()._decimal_to_xsd_format(source) assert string.__class__ is str assert string == expected @pytest.mark.parametrize("source", ( decimal.Decimal(0), decimal.Decimal("0.1") + decimal.Decimal("0.2"), decimal.Decimal("5.781963"))) def test_from_python_object(self, source): assert source.__class__ is decimal.Decimal, "bad test data" translated = MockXDecimal().translate(source, topython=False) expected = MockXDecimal()._decimal_to_xsd_format(source) assert translated.__class__ is str assert translated == expected extra_test_data = () if sys.version_info >= (2, 6): extra_test_data = ( # fraction.Fraction fractions.Fraction(10, 4), fractions.Fraction(1, 3)) @pytest.mark.parametrize("source", ( None, # bool True, False, # float -50.2, 1.9623e-26, 0.1 + 0.2, 0.7, 1.0, 50.99999, # int 0, 1, -55566, # str "0.1", "0.2", "x", # other object(), _Dummy(), datetime.date(2101, 1, 1)) + extra_test_data) def test_from_python_object__no_translation(self, source): assert MockXDecimal().translate(source, topython=False) is source @pytest.mark.parametrize("source", ( "-500.0", "0", "0.0", "0.00000000000000000000001", "000", "1.78123875", "-1.78123875", "1", "01", "100")) def test_to_python_object(self, source): translated = MockXDecimal().translate(source) assert translated.__class__ is decimal.Decimal assert translated == decimal.Decimal(source) @pytest.mark.parametrize("source", ("", 0, 1, 1.5, True, False, 500, _Dummy(), object())) def test_to_python_object__invalid_class_or_empty_string(self, source): assert MockXDecimal().translate(source) is None @pytest.mark.parametrize("src", (" ", "0,0", "0-0", "x", "poppycock")) def test_to_python_object__invalid_string(self, src): """ Suds raises raw Python exceptions when it fails to convert received response element data to its mapped Python decimal.Decimal data type, according to the used WSDL schema. """ pytest.raises(decimal.InvalidOperation, MockXDecimal().translate, src) class TestXFloat: """suds.xsd.sxbuiltin.XFloat.translate() tests.""" extra_test_data = () if sys.version_info >= (2, 6): extra_test_data = ( # fraction.Fraction fractions.Fraction(10, 4), fractions.Fraction(1, 3)) @pytest.mark.parametrize("source", ( None, # bool True, False, # decimal.Decimal decimal.Decimal(0), decimal.Decimal("0.1") + decimal.Decimal("0.2"), decimal.Decimal("5.781963"), # float -50.2, 0.1 + 0.2, 0.7, 1.0, 50.99999, # int 0, 1, -55566, # str "0.1", "0.2", "x", # other object(), _Dummy(), datetime.date(2101, 1, 1)) + extra_test_data) def test_from_python_object(self, source): assert MockXFloat().translate(source, topython=False) is source @pytest.mark.parametrize("source", ( "-500.0", "0", "0.0", "0.00000000000000000000001", "000", "1.78123875", "-1.78123875", "1", "01", "100")) def test_to_python_object(self, source): translated = MockXFloat().translate(source) assert translated.__class__ is float assert translated == float(source) @pytest.mark.parametrize("source", ("", 0, 1, 1.5, True, False, 500, _Dummy(), object())) def test_to_python_object__invalid_class_or_empty_string(self, source): assert MockXFloat().translate(source) is None @pytest.mark.parametrize("source", (" ", "0,0", "0-0", "x", "poppycock")) def test_to_python_object__invalid_string(self, source, monkeypatch): """ Suds raises raw Python exceptions when it fails to convert received response element data to its mapped Python float data type, according to the used WSDL schema. """ monkeypatch.delitem(locals(), "e", False) e = pytest.raises(ValueError, MockXFloat().translate, source).value try: # Using different Python interpreter versions and different source # strings results in different exception messages here. try: float(source) pytest.fail("Bad test data.") except ValueError: assert str(e) == str(sys.exc_info()[1]) finally: del e # explicitly break circular reference chain in Python 3 class TestXInteger: """suds.xsd.sxbuiltin.XInteger.translate() tests.""" @pytest.mark.parametrize("source", ( None, # bool False, True, # int -50, 0, 1, 50, # long long(-50), long(0), long(1), long(50), # str "x", # other object(), _Dummy(), datetime.date(2101, 1, 1))) def test_from_python_object(self, source): assert MockXInteger().translate(source, topython=False) is source @pytest.mark.parametrize("source", ("-500", "0", "000", "1", "01", "100")) def test_to_python_object(self, source): translated = MockXInteger().translate(source) assert translated.__class__ is int assert translated == int(source) @pytest.mark.parametrize("source", ("", 0, 1, True, False, 500, _Dummy(), object())) def test_to_python_object__invalid_class_or_empty_string(self, source): assert MockXInteger().translate(source) is None @pytest.mark.parametrize("source", (" ", "0-0", "x", "poppycock")) def test_to_python_object__invalid_string(self, source, monkeypatch): """ Suds raises raw Python exceptions when it fails to convert received response element data to its mapped Python integer data type, according to the used WSDL schema. """ monkeypatch.delitem(locals(), "e", False) e = pytest.raises(ValueError, MockXInteger().translate, source).value try: # Using different Python interpreter versions and different source # strings results in different exception messages here. try: int(source) pytest.fail("Bad test data.") except ValueError: assert str(e) == str(sys.exc_info()[1]) finally: del e # explicitly break circular reference chain in Python 3 class TestXLong: """suds.xsd.sxbuiltin.XLong.translate() tests.""" @pytest.mark.parametrize("source", ( None, # bool False, True, # int -50, 0, 1, 50, # long long(-50), long(0), long(1), long(50), # str "x", # other object(), _Dummy(), datetime.date(2101, 1, 1))) def test_from_python_object(self, source): assert MockXLong().translate(source, topython=False) is source @pytest.mark.parametrize(("source", "expected"), ( ("-500", -500), ("0", 0), ("000", 0), ("1", 1), ("01", 1), ("100", 100))) def test_to_python_object(self, source, expected): translated = MockXLong().translate(source) assert translated.__class__ is long assert translated == expected @pytest.mark.parametrize("source", ("", 0, 1, True, False, 500, _Dummy(), object())) def test_to_python_object__invalid_class_or_empty_string(self, source): assert MockXLong().translate(source) is None @pytest.mark.parametrize("source", (" ", "0-0", "x", "poppycock")) def test_to_python_object__invalid_string(self, source, monkeypatch): """ Suds raises raw Python exceptions when it fails to convert received response element data to its mapped Python long data type, according to the used WSDL schema. """ monkeypatch.delitem(locals(), "e", False) e = pytest.raises(ValueError, MockXLong().translate, source).value try: # Using different Python interpreter versions and different source # strings results in different exception messages here. try: long(source) pytest.fail("Bad test data.") except ValueError: assert str(e) == str(sys.exc_info()[1]) finally: del e # explicitly break circular reference chain in Python 3 class TestXTime: """ suds.xsd.sxbuiltin.XTime.translate() tests. Related Python object <--> string conversion details are tested in a separate date/time related test module. These tests are only concerned with basic translate() functionality. """ def test_from_python_object(self): time = datetime.time(16, 53, 12) translated = MockXTime().translate(time, topython=False) assert translated.__class__ is suds.sax.date.Time assert str(translated) == "16:53:12" @pytest.mark.parametrize("source", ( None, object(), _Dummy(), datetime.date(2101, 1, 1), datetime.datetime(2101, 1, 1, 22, 47, 9, 981))) def test_from_python_object__invalid(self, source): assert MockXTime().translate(source, topython=False) is source def test_to_python_object(self): assert MockXTime().translate("10:30:22") == datetime.time(10, 30, 22) def test_to_python_object__empty_string(self): assert MockXTime().translate("") == None @pytest.mark.parametrize(("xsd_type_name", "xsd_type"), sorted(builtins.items()) + # See 'Project implementation note #1'. [("...unknown...", XBuiltin)]) def test_create_builtin_type_schema_objects(xsd_type_name, xsd_type): schema = _create_dummy_schema() xsd_object = Factory.create(schema, xsd_type_name) assert xsd_object.__class__ is xsd_type assert xsd_object.name == xsd_type_name assert xsd_object.schema is schema @pytest.mark.parametrize("xsd_type_name", ("tonkica-polonkica", "integer")) def test_create_custom_mapped_builtin_type_schema_objects(xsd_type_name, monkeypatch): """User code can add or update built-in XSD type registrations.""" _monkeypatch_builtin_XSD_type_registry(monkeypatch) class MockType: def __init__(self, schema, name): self.schema = schema self.name = name schema = _Dummy() Factory.maptag(xsd_type_name, MockType) xsd_object = Factory.create(schema, xsd_type_name) assert xsd_object.__class__ is MockType assert xsd_object.name == xsd_type_name assert xsd_object.schema is schema # See 'Project implementation note #1'. @pytest.mark.parametrize("name", sorted(builtins.keys())) @pytest.mark.parametrize("namespace", builtin_namespaces) def test_recognize_builtin_types(name, namespace): schema = _create_dummy_schema() assert schema.builtin((name, namespace)) # See 'Project implementation note #1'. @pytest.mark.parametrize("name", sorted(builtins.keys())) @pytest.mark.parametrize("namespace", ("", " ", "some-dummy-namespace")) def test_recognize_builtin_types_in_unknown_namespace(name, namespace): schema = _create_dummy_schema() assert not schema.builtin((name, namespace)) @pytest.mark.parametrize("name", ("", " ", "x", "xyz")) @pytest.mark.parametrize("namespace", builtin_namespaces + ["", " ", "some-dummy-namespace"]) def test_recognize_non_builtin_types(name, namespace): schema = _create_dummy_schema() assert not schema.builtin((name, namespace)) def test_recognize_custom_mapped_builtins(monkeypatch): """User code can register additional XSD built-ins.""" _monkeypatch_builtin_XSD_type_registry(monkeypatch) schema = _create_dummy_schema() name = "trla-baba-lan" for ns in builtin_namespaces: assert not schema.builtin((name, ns)) Factory.maptag(name, _Dummy) for ns in builtin_namespaces: assert schema.builtin((name, ns)) def test_resolving_builtin_types(monkeypatch): _monkeypatch_builtin_XSD_type_registry(monkeypatch) class MockXInteger(XInteger): pass Factory.maptag("osama", MockXInteger) wsdl = testutils.wsdl('', input="wu") client = testutils.client_from_wsdl(wsdl) element, schema_object = client.sd[0].params[0] assert element.name == "wu" assert element.type == ("osama", "http://www.w3.org/2001/XMLSchema") assert schema_object.__class__ is MockXInteger assert schema_object.name == "osama" assert schema_object.schema is client.wsdl.schema def test_translation(monkeypatch): """Python <--> XML representation translation on marshall/unmarshall.""" anObject = _Dummy() class MockType(XBuiltin): def __init__(self, *args, **kwargs): self._mock_translate_log = [] super(MockType, self).__init__(*args, **kwargs) def translate(self, value, topython=True): self._mock_translate_log.append((value, topython)) if topython: return anObject return "'ollywood" _monkeypatch_builtin_XSD_type_registry(monkeypatch) Factory.maptag("woof", MockType) namespace = "I'm a little tea pot, short and stout..." wsdl = testutils.wsdl("""\ """, input="wi", output="wo", xsd_target_namespace=namespace, operation_name="f") client = testutils.client_from_wsdl(wsdl, nosend=True, prettyxml=True) # Check suds library's XSD schema input parameter information. schema = client.wsdl.schema element_in = schema.elements["wi", namespace] assert element_in.name == "wi" element_out = schema.elements["wo", namespace] assert element_out.name == "wo" schema_object_in = element_in.resolve() schema_object_out = element_out.resolve() assert element_in is client.sd[0].params[0][0] assert schema_object_in is client.sd[0].params[0][1] assert schema_object_in.__class__ is MockType assert schema_object_in._mock_translate_log == [] assert schema_object_out.__class__ is MockType assert schema_object_out._mock_translate_log == [] # Construct operation invocation request - test marshalling. request = client.service.f(55) assert schema_object_in._mock_translate_log == [(55, False)] assert schema_object_out._mock_translate_log == [] CompareSAX.data2data(request.envelope, """\
'ollywood """ % (namespace,)) # Process operation response - test unmarshalling. response = client.service.f(__inject=dict(reply=suds.byte_str("""\ fri-fru """ % (namespace,)))) assert response is anObject assert schema_object_in._mock_translate_log == [(55, False)] assert schema_object_out._mock_translate_log == [("fri-fru", True)] def _create_dummy_schema(): """Constructs a new dummy XSD schema instance.""" #TODO: Find out how to construct this XSD schema object directly without # first having to construct a suds.client.Client from a complete WSDL # schema. wsdl = testutils.wsdl('', input="dummy") client = testutils.client_from_wsdl(wsdl) return client.wsdl.schema def _monkeypatch_builtin_XSD_type_registry(monkeypatch): """ Monkeypatches the global suds built-in XSD type dictionary. After calling this function, a test is free to mess around with suds library's built-in XSD type register (register new ones, change classes registered for a particular XSD type, remove registrations, and such) and any such changes will be automatically undone at the end of the test. If a test does not call this function, any such modifications will be left valid in the current global application state and may affect tests run afterwards. """ tags = Factory.tags assert tags.__class__ is dict monkeypatch.setattr(Factory, "tags", dict(tags)) suds-1.1.2/tests/test_xsd_element.py000066400000000000000000000201351425611400200175470ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Suds library's XSD Element node unit tests. Implemented using the 'pytest' testing framework. """ if __name__ == "__main__": import testutils testutils.run_using_pytest(globals()) import suds import suds.options import suds.sax.parser import suds.store import suds.xsd.schema import pytest from six import b # shared test input data form_test_values = (None, "qualified", "unqualified", "invalid", "") class TestElementForm: """Test whether specific XSD elements are considered qualified.""" @pytest.mark.parametrize("form_default, form", [(x, y) for x in form_test_values for y in form_test_values]) def test_local_element(self, form_default, form): parent_name = "Parent" element_name = "Elemento" namespace = "tns" schema_xml = """\ """ % { "element_name": element_name, "form": _attribute_xml("form", form), "form_default": _attribute_xml("elementFormDefault", form_default), "namespace": namespace, "parent_name": parent_name} expected = form == "qualified" or ( (form is None) and (form_default == "qualified")) schema = _parse_schema_xml(b(schema_xml)) parent_element = schema.elements[parent_name, namespace] element = parent_element.get_child(element_name)[0] assert bool(element.form_qualified) == expected @pytest.mark.parametrize("form_default, form, form_referenced", [(x, y, z) for x in form_test_values for y in form_test_values for z in form_test_values]) def test_reference_to_internal(self, form_default, form, form_referenced): """Reference element to an element in the same schema.""" referenced_name = "Referenced" referencing_parent_name = "Referencing" namespace = "tns" schema_xml = """\ """ % { "form_default": _attribute_xml("elementFormDefault", form_default), "form_referenced": _attribute_xml("form", form_referenced), "form_referencing": _attribute_xml("form", form), "namespace": namespace, "referenced_name": referenced_name, "referencing_parent_name": referencing_parent_name} schema = _parse_schema_xml(b(schema_xml)) parent_element = schema.elements[referencing_parent_name, namespace] referencing_element = parent_element.get_child(referenced_name)[0] assert referencing_element.form_qualified def test_reference_to_external(self): """Reference element to an element in an external schema.""" schema_xml_here = """\ """ schema_xml_there = """\ """ store = suds.store.DocumentStore({"there.xsd": b(schema_xml_there)}) schema = _parse_schema_xml(b(schema_xml_here), store) referenced_element = schema.elements["Referenced", "ns-there"] referencing_parent = schema.elements["Referencing", None] referencing_element = referencing_parent.get_child("Referenced")[0] assert referencing_element.form_qualified @pytest.mark.parametrize("form_default, form", [(x, y) for x in form_test_values for y in form_test_values]) def test_top_level_element(self, form_default, form): element_name = "Elemento" namespace = "tns" schema_xml = """\ """ % { "element_name": element_name, "form": _attribute_xml("form", form), "form_default": _attribute_xml("elementFormDefault", form_default), "namespace": namespace} schema = _parse_schema_xml(b(schema_xml)) element = schema.elements[element_name, namespace] assert element.form_qualified def test_reference(): """Reference to an element in a different schema.""" schema_xml_here = """\ """ schema_xml_there = """\ """ store = suds.store.DocumentStore({"there.xsd": b(schema_xml_there)}) schema = _parse_schema_xml(b(schema_xml_here), store) referenced_element = schema.elements["Referenced", "ns-there"] referencing_parent = schema.elements["Referencing", None] referencing_element = referencing_parent.get_child("Referenced")[0] assert referenced_element.ref is None assert referencing_element.ref == ("Referenced", "ns-there") ############################################################################### # # Test utilities. # ############################################################################### def _attribute_xml(name, value): if value is not None: return ' %s="%s"' % (name, value) return "" def _parse_schema_xml(xml, documentStore=None): """Test utility constructing an XSD schema model from the given XML.""" parser = suds.sax.parser.Parser() document = parser.parse(string=xml) root = document.root() url = "somewhere://over.the/rainbow" options_kwargs = {} if documentStore: options_kwargs.update(documentStore=documentStore) options = suds.options.Options(**options_kwargs) return suds.xsd.schema.Schema(root, url, options) suds-1.1.2/tests/testutils/000077500000000000000000000000001425611400200156665ustar00rootroot00000000000000suds-1.1.2/tests/testutils/__init__.py000066400000000000000000000210651425611400200200030ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Package containing different utilities used for suds project testing. """ import suds.client import suds.store import six import os import subprocess import sys from .compare_sax import CompareSAX def _assert_request_content(request, expected_xml): CompareSAX.data2data(request.envelope, expected_xml) def client_from_wsdl(wsdl_content, *args, **kwargs): """ Constructs a non-caching suds Client based on the given WSDL content. The wsdl_content is expected to be a raw byte string and not a unicode string. This simple structure suits us fine here because XML content holds its own embedded encoding identification ('utf-8' if not specified explicitly). Stores the content directly inside the suds library internal document store under a hard-coded id to avoid having to load the data from a temporary file. Uses a locally created empty document store unless one is provided externally using the 'documentStore' keyword argument. Explicitly disables caching or otherwise, because we use the same hardcoded id for our main WSDL document, suds would always reuse the first such local document from its cache instead of fetching it from our document store. """ assert wsdl_content.__class__ is suds.byte_str_class, "bad test data" store = kwargs.get("documentStore") if store is None: store = suds.store.DocumentStore() kwargs.update(documentStore=store) test_file_id = "whatchamacallit" store.update({test_file_id: wsdl_content}) kwargs.update(cache=None) return suds.client.Client("suds://" + test_file_id, *args, **kwargs) def run_test_process(script): """ Runs the given Python test script as a separate process. Expects the script to return an exit code 0 and output nothing on either stdout or stderr output streams. """ popen = subprocess.Popen([sys.executable], stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE, cwd=script.dirname, universal_newlines=True) sys_path = sys.path for i in range(len(sys_path)): if not sys_path[i]: sys_path[i] = os.getcwd() out, err = popen.communicate("""\ import sys sys.path = %(sys.path)s import suds if suds.__version__ != %(suds.__version__)r: print("Unexpected suds version imported - '%%s'." %% (suds.__version__)) sys.exit(-2) if sys.version_info >= (3, 0): def exec_file(x): e = getattr(__builtins__, "exec") return e(open(x).read(), globals(), globals()) else: exec_file = execfile exec_file(%(script)r) """ % {"suds.__version__": suds.__version__, "script": script.basename, "sys.path": sys_path}) if popen.returncode != 0 or err or out: if popen.returncode != 0: print("Test process exit code: %d" % (popen.returncode,)) if out: print("Test process stdout:") print(out) if err: print("Test process stderr:") print(err) import pytest pytest.fail("Test subprocess failed.") def run_using_pytest(caller_globals): """Run the caller test script using the pytest testing framework.""" import sys # Trick setuptools into not recognizing we are referencing __file__ here. # If setuptools detects __file__ usage in a module, any package containing # this module will be installed as an actual folder instead of a zipped # archive. This __file__ usage is safe since it is used only when a script # has been run directly, and that can not be done from a zipped package # archive. filename = caller_globals.get("file".join(["__"] * 2)) if not filename: sys.exit("Internal error: can not determine test script name.") try: import pytest except ImportError: filename = filename or "" sys.exit("'py.test' unit testing framework not available. Can not run " "'%s' directly as a script." % (filename,)) exit_code = pytest.main(["--pyargs", filename] + sys.argv[1:]) sys.exit(exit_code) def wsdl(schema_content, input=None, output=None, operation_name="f", wsdl_target_namespace="my-wsdl-namespace", xsd_target_namespace="my-xsd-namespace", web_service_URL="protocol://unga-bunga-location"): """ Returns WSDL schema content used in different suds library tests. Defines a single operation taking an externally specified input structure and returning an externally defined output structure. Constructed WSDL schema's XML namespace prefixes: * my_wsdl - the WSDL schema's target namespace. * my_xsd - the embedded XSD schema's target namespace. input/output parameters accept the following values: * None - operation has no input/output message. * list/tuple - operation has an input/output message consisting of message parts referencing top-level XSD schema elements with the given names. * Otherwise operation has an input/output message consisting of a single message part referencing a top-level XSD schema element with the given name. """ assert isinstance(schema_content, six.string_types) has_input = input is not None has_output = output is not None wsdl = ["""\ %(schema_content)s """ % dict(schema_content=schema_content, wsdl_target_namespace=wsdl_target_namespace, xsd_target_namespace=xsd_target_namespace)] if has_input: if input.__class__ not in (list, tuple): input = [input] wsdl.append("""\ """) for element in input: wsdl.append("""\ """ % (element,)) wsdl.append("""\ """) if has_output: if output.__class__ not in (list, tuple): output = [output] wsdl.append("""\ """) for element in output: wsdl.append("""\ """ % (element,)) wsdl.append("""\ """) wsdl.append("""\ """ % (operation_name,)) if has_input: wsdl.append("""\ """) if has_output: wsdl.append("""\ """) wsdl.append("""\ """ % (operation_name,)) if has_input: wsdl.append("""\ """) if has_output: wsdl.append("""\ """) wsdl.append("""\ """ % (web_service_URL,)) return suds.byte_str("\n".join(wsdl)) suds-1.1.2/tests/testutils/assertion.py000066400000000000000000000017661425611400200202610ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Assertion test utility functions shared between multiple test modules. """ from testutils.assertion__pytest_assert_rewrite_needed import * suds-1.1.2/tests/testutils/assertion__pytest_assert_rewrite_needed.py000066400000000000000000000030141425611400200264420ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Assertion test utility functions shared between multiple test modules. Extracted into a separate module, named so pytest would apply its assertion rewriting to it. 'pytest' assertion rewriting allows our assertion test utility functions to use Python assertions and have them work even when run with Python assertions disabled. """ def assert_no_output(pytest_capture_fixture): """ Test utility asserting there was no captured stdout or stderr output. pytest_capture_fixture parameter may be one of pytest's output capture fixtures, e.g. capsys or capfd. """ out, err = pytest_capture_fixture.readouterr() assert not out assert not err suds-1.1.2/tests/testutils/compare_sax.py000066400000000000000000000017201425611400200205410ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ CompareSAX test utility class. """ from testutils.compare_sax__pytest_assert_rewrite_needed import * suds-1.1.2/tests/testutils/compare_sax__pytest_assert_rewrite_needed.py000066400000000000000000000250361425611400200267440ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ CompareSAX test utility class implementation. Extracted into a separate module, named so pytest would apply its assertion rewriting to it. 'pytest' assertion rewriting allows CompareSAX to use Python assertion based XML mismatch reporting and have it work even when run with Python assertions disabled. """ import suds.sax.document import suds.sax.element import suds.sax.parser from six import text_type, u import sys class CompareSAX: """ Support for comparing SAX XML structures. Not intended to be perfect, but only good enough XML comparison to be used internally inside the project's test suite. Raw XML data is parsed using a suds SAX parser and the resulting DOM structure compared. This means that any XML data differences lost during SAX parsing can not be detected. Some examples: - all textual content for a single node is concatenated together, so the following two XML data segments are considered equivalent: 1. 'xxyy' 2. 'xxyy' - all leading and trailing whitespace is trimmed from textual content for nodes having at least one child element, so most XML indentation information is lost and the following two XML data segments are considered equivalent: 1. ' ' 2. '' Suds may generate different SOAP request XML data for the same input based on the order in which it reads some of its internal data held inside unordered containers, e.g. set or dictionary. To compensate for this we consider both namespace prefix & namespace declaration (either extra declarations or declaration placement) differences in XML documents as irrelevant. We do however compare each XML node's namespace, i.e. that their namespace names match even if their namespace prefixes do not or if those namespaces have been declared on different XML elements. """ def __init__(self): self.__context = [] @staticmethod def assertions_enabled(): """ Returns whether Python assertions have been enabled in this module. CompareSAX class uses Python assertions to report failed comparison results so this information in order to know whether related tests should be disabled. """ try: assert False except AssertionError: return True return False @classmethod def document2document(cls, lhs, rhs): """Compares two SAX XML documents.""" self = cls() try: self.__document2document(lhs, rhs, context="document2document") except Exception: self.__report_context() raise @classmethod def document2element(cls, document, element): """ Compares a SAX XML document structure to a SAX XML element. The given document & element are considered successfully matched if the document consists of a single XML element matching the given one. """ self = cls() self.__push_context("document2element") try: assert document.__class__ is suds.sax.document.Document assert element.__class__ is suds.sax.element.Element assert len(document.getChildren()) == 1 self.__element2element(document.getChildren()[0], element) except Exception: self.__report_context() raise @classmethod def element2element(cls, lhs, rhs): """Compares two SAX XML elements.""" self = cls() self.__push_context("element2element") try: self.__element2element(lhs, rhs) except Exception: self.__report_context() raise @classmethod def data2data(cls, lhs, rhs): """Compares two SAX XML documents given as strings or bytes objects.""" self = cls() try: lhs_doc = self.__parse_data(lhs) rhs_doc = self.__parse_data(rhs) self.__document2document(lhs_doc, rhs_doc, context="data2data") except Exception: self.__report_context() raise @classmethod def document2data(cls, lhs, rhs): """ Compares two SAX XML documents, second one given as a string or a bytes object. """ self = cls() try: rhs_doc = self.__parse_data(rhs) self.__document2document(lhs, rhs_doc, context="document2data") except Exception: self.__report_context() raise def __compare_child_elements(self, lhs, rhs): """Compares the given entities' child elements.""" assert len(lhs.getChildren()) == len(rhs.getChildren()) count = len(lhs.getChildren()) for i, (l, r) in enumerate(zip(lhs.getChildren(), rhs.getChildren())): self.__element2element(l, r, context_info=(i, count)) def __compare_element_namespace(self, lhs, rhs): """ Compares the given elements' namespaces. Empty string & None XML element namespaces are considered the same to compensate for the suds SAX document model representing the following 'default namespace' scenarios differently: """ #TODO: Make suds SAX element model consistently represent empty/missing # namespaces and then update both this method and its docstring. self.__push_context("namespace") lhs_namespace = lhs.namespace()[1] rhs_namespace = rhs.namespace()[1] if not lhs_namespace: lhs_namespace = None if not rhs_namespace: rhs_namespace = None assert lhs_namespace == rhs_namespace self.__pop_context() def __compare_element_text(self, lhs, rhs): """ Compares the given elements' textual content. Empty string & None XML element texts are considered the same to compensate for different XML object tree construction methods representing 'no text' elements differently, e.g. depending on whether a particular SAX parsed XML element had any whitespace characters in its textual data or whether the element got constructed in code to represent a SOAP request. """ #TODO: Make suds SAX element model consistently represent empty/missing # text content and then update both this method and its docstring. self.__push_context("text") lhs_text = lhs.text rhs_text = rhs.text if not lhs_text: lhs_text = None if not rhs_text: rhs_text = None assert lhs_text == rhs_text self.__pop_context() def __document2document(self, lhs, rhs, context): """ Internal document2document comparison worker. See document2document() docstring for more detailed information. """ self.__push_context(context) assert lhs.__class__ is suds.sax.document.Document assert rhs.__class__ is suds.sax.document.Document self.__compare_child_elements(lhs, rhs) self.__pop_context() @staticmethod def __element_name(element): """Returns a given SAX element's name as unicode or '???' on error.""" try: return text_type(element.name) except (KeyboardInterrupt, SystemExit): raise except Exception: return u("???") def __element2element(self, lhs, rhs, context_info=(0, 1)): """ Internal element2element comparison worker. See element2element() docstring for more detailed information. The context information is an (n, count) 2-element collection indicating which element2element() call ('n') this is in a sequence of such calls ('count'). The exact context string is constructed based on the given elements' names and context information. """ context = self.__element2element_context(lhs, rhs, context_info) self.__push_context(context) assert lhs.__class__ is suds.sax.element.Element assert rhs.__class__ is suds.sax.element.Element assert lhs.name == rhs.name self.__compare_element_namespace(lhs, rhs) self.__compare_element_text(lhs, rhs) self.__compare_child_elements(lhs, rhs) self.__pop_context() @classmethod def __element2element_context(cls, lhs, rhs, context_info): """ Return a context string for a given element2element call. See the __element2element() docstring for more detailed information. """ n, count = context_info assert 0 <= n < count, "broken CompareSAX implementation" context_lhs_name = cls.__element_name(lhs) context_rhs_name = cls.__element_name(rhs) if context_lhs_name == context_rhs_name: context_name = context_lhs_name else: context_name = "%s/%s" % (context_lhs_name, context_rhs_name) if count == 1: return "<%s>" % (context_name,) return "<%s(%d/%d)>" % (context_name, n + 1, count) @staticmethod def __parse_data(data): """ Construct a SAX XML document based on its data given as a string or a bytes object. """ if isinstance(data, text_type): data = data.encode("utf-8") return suds.sax.parser.Parser().parse(string=data) def __pop_context(self): self.__context.pop() def __push_context(self, context): self.__context.append(context) def __report_context(self): if self.__context: sys.stderr.write("Failed SAX XML comparison context:\n") sys.stderr.write(" %s\n" % (".".join(self.__context))) suds-1.1.2/tests/testutils/indirect_parametrize.py000066400000000000000000000130271425611400200224470ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Indirect parametrization pytest plugin. Allows tests parametrization data to be generated using a function instead of having to be explicitly specified as is the case when using the builtin pytest.mark.parametrize functionality. Interface is similar to the builtin pytest.mark.parametrize implementation, except that it takes an indirect parametrization function as an additional initial positional argument. All the other arguments parameters are forwarded onto this indirect parametrization function which then calculates and returns the actual parametrization data. The return values are a standard Python positional argument list and keyword argument dictionary to be used for the underlying metafunc.parametrize() call. May be used as either: 1. pytest.indirect_parametrizer() or 2. pytest.mark.indirect_parametrizer(). The two usages are equivalent except that the first one may be used with any indirect parametrization function while the second one can not be used with indirect parametrization functions taking no input parameters (with pytest versions prior to 2.5.2 it also can not be used with functions taking only keyword arguments). This is a technical restriction based on an underlying pytest.mark implementation detail. See the indirect_parametrize() function doc-string for more detailed information. Usage example making the following test_example() test function calls after calculating them from a much shorter definition: * test_example("Fritula", 1) * test_example("Fritula", 2) * test_example("Fritula", 3) * test_example("Fritula", 4) * test_example("Fritula", 5) * test_example("Fritula", 6) * test_example("Fritula", 7) * test_example("Fritula", 8) * test_example("Fritula", 9) * test_example("Madagascar", 10) * test_example("Madagascar", 20) * test_example("Madagascar", 30) * test_example("Rumpelstiltskin", 20) * test_example("Rumpelstiltskin", 40) def custom_parametrization(param_names, param_value_defs): param_values = [] for uno, due_values in param_value_defs: for due in due_values: param_values.append((uno, due)) return (param_names, param_values), {} @pytest.indirect_parametrize(custom_parametrization, ("uno", "due"), ( ("Fritula", (1, 2, 3, 4, 5, 6, 7, 8, 9)), ("Madagascar", (10, 30, 50)), ("Rumpelstiltskin", (20, 40)))) def test_example(uno, due): assert False """ import pytest def indirect_parametrize(func, *args, **kwargs): """ Decorator registering a custom parametrization function for a pytest test. This pytest.mark.indirect_parametrize() replacement allows the use of indirect parametrization functions taking no input parameters or, with pytest versions prior to 2.5.2, functions taking only keyword arguments. If a pytest.mark.indirect_parametrize() call is made with such an indirect parametrization function, it decorates the given function instead of storing and using it to decorate the intended function later on. """ # In pytest versions prior to 2.5.2 pytest.mark.indirect_parametrize() # special handling occurs even when passing it additional keyword arguments # so we have to make sure we are passing it at least one additional # positional argument. def wrapper(func, *args, **kwargs): return func(*args, **kwargs) return pytest.mark.indirect_parametrize(wrapper, func, *args, **kwargs) def pytest_configure(config): """Describe the new pytest marker in the --markers output.""" config.addinivalue_line("markers", "indirect_parametrize(function, argnames, argvalues): similar to the " "builtin pytest.mark.parametrize implementation, except that it takes " "an indirect parametrization function as an additional initial " "positional argument. All the other parameters are forwarded to the " "indirect parametrization function which then returns the actual " "metafunc.parametrize() parameters (standard Python positional " "argument list and keyword argument dictionary) based on the received " "input data. For more detailed information see the " "indirect_parametrize pytest plugin implementation module.") """pytest hook publishing references in the toplevel pytest namespace.""" pytest.indirect_parametrize = indirect_parametrize def pytest_generate_tests(metafunc): """pytest hook called for all detected test functions.""" mark = metafunc.definition.get_closest_marker('indirect_parametrize') if not mark: return args, kwargs = mark.args[0](*mark.args[1:], **mark.kwargs) metafunc.parametrize(*args, **kwargs) suds-1.1.2/tools/000077500000000000000000000000001425611400200136245ustar00rootroot00000000000000suds-1.1.2/tools/readme.txt000066400000000000000000000006121425611400200156210ustar00rootroot00000000000000Tools & utilities used during suds development. Only stand-alone scripts should be located in this folder. Any imported additional modules should be located under the suds_devel package folder. All Python scripts under this folder should be prepared so their sources are directly compatible with both Python 2.x & 3.x without the need for any py2to3 based source code transformation. suds-1.1.2/tools/run_all_tests.py000066400000000000000000000112421425611400200170540ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ "poor man's tox" development script used on Windows to run the full suds-jurko test suite using multiple Python interpreter versions. Intended to be used as a general 'all tests passed' check. To see more detailed information on specific failures, run the failed test group manually, configured for greater verbosity than done here. """ import os.path import shutil import sys from suds_devel.configuration import BadConfiguration, Config, configparser from suds_devel.environment import BadEnvironment import suds_devel.utility as utility class MyConfig(Config): def __init__(self, script, project_folder, ini_file): """ Initialize new script configuration. External configuration parameters may be specified relative to the following folders: * script - relative to the current working folder * project_folder - relative to the script folder * ini_file - relative to the project folder """ super(MyConfig, self).__init__(script, project_folder, ini_file) try: self._read_environment_configuration() except configparser.Error: raise BadConfiguration(sys.exc_info()[1].message) def _prepare_configuration(): # We know we are a regular stand-alone script file and not an imported # module (either frozen, imported from disk, zip-file, external database or # any other source). That means we can safely assume we have the __file__ # attribute available. global config config = MyConfig(__file__, "..", "setup.cfg") def _print_title(env, message_fmt): separator = "-" * 63 print("") print(separator) print("--- " + message_fmt % (env.name(),)) print(separator) def _report_startup_information(): print("Running in folder: '%s'" % (os.getcwd(),)) def _run_tests(env): if env.sys_version_info >= (3,): _print_title(env, "Building suds for Python %s") build_folder = os.path.join(config.project_folder, "build") if os.path.isdir(build_folder): shutil.rmtree(build_folder) # Install the project into the target Python environment in editable mode. # This will actually build Python 3 sources in case we are using a Python 3 # environment. setup_cmd = ["setup.py", "-q", "develop"] _, _, return_code = env.execute(setup_cmd, cwd=config.project_folder) if return_code != 0: return False test_folder = os.path.join(config.project_folder, "tests") pytest_cmd = ["-m", "pytest", "-q", "-x", "--tb=short"] _print_title(env, "Testing suds with Python %s") _, _, return_code = env.execute(pytest_cmd, cwd=test_folder) if return_code != 0: return False _print_title(env, "Testing suds with Python %s - no assertions") pytest_cmd.insert(0, "-O") _, _, return_code = env.execute(pytest_cmd, cwd=test_folder) return return_code == 0 def _run_tests_in_all_environments(): if not config.python_environments: raise BadConfiguration("No Python environments configured.") for env in config.python_environments: if not env.initial_scan_completed: _print_title(env, "Scanning environment Python %s") env.run_initial_scan() if not _run_tests(env): return False return True def main(): try: _report_startup_information() _prepare_configuration() success = _run_tests_in_all_environments() except (BadConfiguration, BadEnvironment): utility.report_error(sys.exc_info()[1]) return -2 print("") if not success: print("Test failed.") return -3 print("All tests passed.") return 0 if __name__ == "__main__": sys.exit(main()) suds-1.1.2/tools/setup_base_environments.py000066400000000000000000001215251425611400200211450ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Sets up base Python development environments used by this project. These are the Python environments from which multiple virtual environments can then be spawned as needed. The environments should have the following Python packages installed: * setuptools (for installing pip) * pip (for installing everything except itself) * pytest (for running the project's test suite) * six (Python 2/3 compatibility layer used in the project's test suite) * virtualenv (for creating virtual Python environments) plus certain specific Python versions may require additional backward compatibility support packages. """ #TODO: Python 3.4.0 comes with setuptools & pip preinstalled but they can be # installed and/or upgraded manually if needed so we choose not to use their # built-in installations, i.e. the built-in ensurepip module. Consider using # the built-in ensurepip module if regular setuptools/pip installation fails # for some reason or has been configured to run locally. #TODO: logging #TODO: command-line option support #TODO: support for additional configuration files, e.g. ones that are developer # or development environment specific. #TODO: warn if multiple environments use the same executable #TODO: make the script importable #TODO: report when the installed package version is newer than the last tested # one #TODO: hold a list of last tested package versions and report a warning if a # newer one is encountered # > # Where no Python package version has been explicitly specified, the # > # following 'currently latest available' package release has been # > # successfully used: # > "last tested package version": { # > "argparse": "1.2.1", # > "backports.ssl_match_hostname": "3.4.0.2", # > "colorama": "0.3.1", # > "pip": "1.5.5", # > "py": "1.4.20", # > "pytest": "2.5.2", # > "setuptools": "3.6", # > "virtualenv": "1.11.5"}, #TODO: automated checking for new used package versions, e.g. using PyPI XRC # API: # > import xmlrpc.client as xrc # > client = xrc.ServerProxy("http://pypi.python.org/pypi") # > client.package_releases("six") # just the latest release # > client.package_releases("six", True) # all releases #TODO: option to break on bad environments #TODO: verbose option to report bad environment detection output #TODO: verbose option to report all environment detection output #TODO: verbose option to report environment details #TODO: Consider running the environment scanner script from an empty temporary # folder to avoid importing random same-named modules from the current working # folder. An alternative would be to play around with sys.path in the scanner # script, e.g. remove its first element. Also, we might want to clear out any # globally set Python environment variables such as PYTHONPATH. #TODO: collect stdout, stderr & log outputs for each easy_install/pip run #TODO: configurable - avoid downloads if a suitable locally downloaded source # is available (ez_setup, setuptools, pip) #TODO: 244 downloads must come before 243 installation #TODO: support configuring what latest suitable version we want to use if # available in the following layers: # - already installed # - local installation cache (can not find out the latest available content, # can only download to it or install from it) # - pypi # related configuration options: # - allow already installed, # - allow locally downloaded, # - allow pypi #TODO: if you want better local-cache support - use devpi: # - better caching support and version detection # - devpi & pypi usage transparent # - we'll need a script for installing & setting up the devpi server #TODO: parallelization #TODO: concurrency support - file locking required for: # installation cache folder: # downloading (write) # zipping eggs (write) # installing from local folder (read) # Python environment installation area: # installing new packages (write) # running Python code (read) #TODO: test whether we can upgrade pip in-place #TODO: test how we can make pip safe to use when there are multiple pip based # installations being run at the same time by the same user - specify some # global folders, like the temp build folder, explicitly #TODO: Detect most recent packages on PyPI, but do that at most once in a # single script run, or with a separate script, or use devpi. Currently, if a # suitable package is found locally, a more suitable one will not be checked # for on PyPI. #TODO: Recheck error handling to make sure all failed commands are correctly # detected. Some might not set a non-0 exit code on error and so their output # must be used as a success/failure indicator. import itertools import os import os.path import re import sys import tempfile from suds_devel.configuration import BadConfiguration, Config, configparser from suds_devel.egg import zip_eggs_in_folder from suds_devel.environment import BadEnvironment, Environment from suds_devel.exception import EnvironmentSetupError from suds_devel.parse_version import parse_version from suds_devel.requirements import (pytest_requirements, six_requirements, virtualenv_requirements) import suds_devel.utility as utility # ------------- # Configuration # ------------- class MyConfig(Config): # Section names. SECTION__ACTIONS = "setup base environments - actions" SECTION__FOLDERS = "setup base environments - folders" SECTION__REUSE_PREINSTALLED_SETUPTOOLS = \ "setup base environments - reuse pre-installed setuptools" def __init__(self, script, project_folder, ini_file): """ Initialize new script configuration. External configuration parameters may be specified relative to the following folders: * script - relative to the current working folder * project_folder - relative to the script folder * ini_file - relative to the project folder """ super(MyConfig, self).__init__(script, project_folder, ini_file) self.__cached_paths = {} try: self.__read_configuration() except configparser.Error: raise BadConfiguration(sys.exc_info()[1].message) def ez_setup_folder(self): return self.__get_cached_path("ez_setup folder") def installation_cache_folder(self): return self.__get_cached_path("installation cache") def pip_download_cache_folder(self): return self.__get_cached_path("pip download cache") def __get_cached_path(self, option): try: return self.__cached_paths[option] except KeyError: x = self.__cached_paths[option] = self.__get_path(option) return x def __get_path(self, option): try: folder = self._reader.get(self.SECTION__FOLDERS, option) except (configparser.NoOptionError, configparser.NoSectionError): return except configparser.Error: raise BadConfiguration("Error reading configuration option " "'%s.%s' - %s" % (self.SECTION__FOLDERS, option, sys.exc_info()[1])) base_paths = { "project-folder": self.project_folder, "script-folder": self.script_folder, "ini-folder": os.path.dirname(self.ini_file)} folder_parts = re.split("[\\/]{2}", folder, maxsplit=1) base_path = None if len(folder_parts) == 2: base_path = base_paths.get(folder_parts[0].lower()) if base_path is not None: folder = folder_parts[1] if not folder: raise BadConfiguration("Configuration option '%s.%s' invalid. A " "valid relative path must not be empty. Use '.' to represent " "the base folder." % (section, option)) if base_path is None: base_path = base_paths.get("ini-folder") return os.path.normpath(os.path.join(base_path, folder)) def __read_configuration(self): self._read_environment_configuration() section = self.SECTION__REUSE_PREINSTALLED_SETUPTOOLS self.reuse_old_setuptools = self._get_bool(section, "old") self.reuse_best_setuptools = self._get_bool(section, "best") self.reuse_future_setuptools = self._get_bool(section, "future") section = self.SECTION__ACTIONS self.report_environment_configuration = ( self._get_bool(section, "report environment configuration")) self.report_raw_environment_scan_results = ( self._get_bool(section, "report raw environment scan results")) self.setup_setuptools = ( self._get_tribool(section, "setup setuptools")) self.download_installations = ( self._get_tribool(section, "download installations")) self.install_environments = ( self._get_tribool(section, "install environments")) def _prepare_configuration(): # We know we are a regular stand-alone script file and not an imported # module (either frozen, imported from disk, zip-file, external database or # any other source). That means we can safely assume we have the __file__ # attribute available. global config config = MyConfig(__file__, "..", "setup.cfg") # -------------------- # Environment scanning # -------------------- def report_environment_configuration(env): if not (env and env.initial_scan_completed): return print(" ctypes version: %s" % (env.ctypes_version,)) print(" pip version: %s" % (env.pip_version,)) print(" pytest version: %s" % (env.pytest_version,)) print(" python version: %s" % (env.python_version,)) print(" setuptools version: %s" % (env.setuptools_version,)) if env.setuptools_zipped_egg is not None: print(" setuptools zipped egg: %s" % (env.setuptools_zipped_egg,)) print(" virtualenv version: %s" % (env.virtualenv_version,)) def report_raw_environment_scan_results(out, err, exit_code): if out is None and err is None and exit_code is None: return print("-----------------------------------") print("--- RAW SCAN RESULTS --------------") print("-----------------------------------") if exit_code is not None: print("*** EXIT CODE: %d" % (exit_code,)) for name, value in (("STDOUT", out), ("STDERR", err)): if value: print("*** %s:" % (name,)) sys.stdout.write(value) if value[-1] != "\n": sys.stdout.write("\n") print("-----------------------------------") class ScanProgressReporter: """ Reports scanning progress to the user. Takes care of all the gory progress output formatting details so they do not pollute the actual scanning logic implementation. A ScanProgressReporter's output formatting logic assumes that the reporter is the one with full output control between calls to a its report_start() & report_finish() methods. Therefore, user code must not do any custom output during that time or it risks messing up the reporter's output formatting. """ def __init__(self, environments): self.__max_name_length = max(len(x.name()) for x in environments) self.__count = len(environments) self.__count_width = len(str(self.__count)) self.__current = 0 self.__reporting = False print("Scanning Python environments...") def report_start(self, name): assert len(name) <= self.__max_name_length assert self.__current <= self.__count assert not self.__reporting self.__reporting = True self.__current += 1 name_padding = " " * (self.__max_name_length - len(name)) sys.stdout.write("[%*d/%d] Scanning '%s'%s - " % (self.__count_width, self.__current, self.__count, name, name_padding)) sys.stdout.flush() def report_finish(self, report): assert self.__reporting self.__reporting = False print(report) class ScannedEnvironmentTracker: """Helps track scanned Python environments and report duplicates.""" def __init__(self): self.__names = set() self.__last_name = None self.__environments = [] def environments(self): return self.__environments def track_environment(self, env): assert env not in self.__environments assert env.name() == self.__last_name self.__environments.append(env) def track_name(self, name): if name in self.__names: raise BadConfiguration("Python environment '%s' configured " "multiple times." % (name,)) self.__names.add(name) self.__last_name = name def scan_python_environment(env, progress_reporter, environment_tracker): environment_tracker.track_name(env.name()) # N.B. No custom output allowed between calls to our progress_reporter's # report_start() & report_finish() methods or we risk messing up its output # formatting. progress_reporter.report_start(env.name()) try: try: out, err, exit_code = env.run_initial_scan() except: progress_reporter.report_finish("----- %s" % (_exc_str(),)) raise except BadEnvironment: out, err, exit_code = sys.exc_info()[1].raw_scan_results() else: progress_reporter.report_finish(env.description()) environment_tracker.track_environment(env) if config.report_raw_environment_scan_results: report_raw_environment_scan_results(out, err, exit_code) if config.report_environment_configuration: report_environment_configuration(env) def scan_python_environments(): environments = config.python_environments if not environments: raise BadConfiguration("No Python environments configured.") progress_reporter = ScanProgressReporter(environments) environment_tracker = ScannedEnvironmentTracker() for env in environments: scan_python_environment(env, progress_reporter, environment_tracker) return environment_tracker.environments() # ------------------------------------------ # Generic functionality local to this module # ------------------------------------------ def _create_installation_cache_folder_if_needed(): assert config.installation_cache_folder() is not None if not os.path.isdir(config.installation_cache_folder()): print("Creating installation cache folder...") # os.path.abspath() to avoid ".." entries in the path that would # otherwise confuse os.makedirs(). os.makedirs(os.path.abspath(config.installation_cache_folder())) def _exc_str(): exc_type, exc = sys.exc_info()[:2] type_desc = [] if exc_type.__module__ and exc_type.__module__ != "__main__": type_desc.append(exc_type.__module__) type_desc.append(exc_type.__name__) desc = ".".join(type_desc), str(exc) return ": ".join(x for x in desc if x) def _report_configuration(): folder = config.installation_cache_folder() if folder is not None: print("Installation cache folder: '%s'" % (folder,)) folder = config.pip_download_cache_folder() if folder is not None: print("PIP download cache folder: '%s'" % (folder,)) def _report_startup_information(): print("Running in folder: '%s'" % (os.getcwd(),)) # ---------------------------------- # Processing setuptools installation # ---------------------------------- def process_setuptools(env, actions): if "setup setuptools" not in actions: return installer = _ez_setup_script(env) if _reuse_pre_installed_setuptools(env, installer): return _avoid_setuptools_zipped_egg_upgrade_issue(env, installer) try: # 'ez_setup' script will download its setuptools installation to the # 'current working folder'. If we are using an installation cache # folder, we run the script from there to get the downloaded setuptools # installation stored together with all of the other used # installations. If we are not, then just have it downloaded to the # current folder. if config.installation_cache_folder() is not None: _create_installation_cache_folder_if_needed() installer.execute(cwd=config.installation_cache_folder()) except (KeyboardInterrupt, SystemExit): raise except Exception: raise EnvironmentSetupError("setuptools installation failed - %s" % ( _exc_str(),)) class _ez_setup_script: """setuptools project's ez_setup installer script.""" def __init__(self, env): self.__env = env if not config.ez_setup_folder(): self.__error("ez_setup folder not configured") self.__ez_setup_folder = config.ez_setup_folder() self.__cached_script_path = None self.__cached_setuptools_version = None if not os.path.isfile(self.script_path()): self.__error("installation script '%s' not found" % ( self.script_path(),)) def execute(self, cwd=None): script_path = self.script_path() kwargs = {} if cwd: kwargs["cwd"] = cwd script_path = os.path.abspath(script_path) self.__env.execute([script_path], **kwargs) def script_path(self): if self.__cached_script_path is None: self.__cached_script_path = self.__script_path() return self.__cached_script_path def setuptools_version(self): if self.__cached_setuptools_version is None: self.__cached_setuptools_version = self.__setuptools_version() return self.__cached_setuptools_version def __error(self, msg): raise EnvironmentSetupError("Can not install setuptools - %s." % ( msg,)) def __script_path(self): import suds_devel.ez_setup_versioned as ez script_name = ez.script_name(self.__env.sys_version_info) return os.path.join(self.__ez_setup_folder, script_name) def __setuptools_version(self): """Read setuptools version from the underlying ez_setup script.""" # Read the script directly as a file instead of importing it as a # Python module and reading the value from the loaded module's global # DEFAULT_VERSION variable. Not all ez_setup scripts are compatible # with all Python environments and so importing them would require # doing so using a separate process run in the target Python # environment instead of the current one. f = open(self.script_path(), "r") try: matcher = re.compile(r'\s*DEFAULT_VERSION\s*=\s*"([^"]*)"\s*$') for i, line in enumerate(f): if i > 50: break match = matcher.match(line) if match: return match.group(1) finally: f.close() self.__error("error parsing setuptools installation script '%s'" % ( self.script_path(),)) def _avoid_setuptools_zipped_egg_upgrade_issue(env, ez_setup): """ Avoid the setuptools self-upgrade issue. setuptools versions prior to version 3.5.2 have a bug that can cause their upgrade installations to fail when installing a new zipped egg distribution over an existing zipped egg setuptools distribution with the same name. The following Python versions are not affected by this issue: Python 2.4 - use setuptools 1.4.2 - installs itself as a non-zipped egg Python 2.6+ - use setuptools versions not affected by this issue That just leaves Python versions 2.5.x to worry about. This problem occurs because of an internal stale cache issue causing the upgrade to read data from the new zip archive at a location calculated based on the original zip archive's content, effectively causing such read operations to either succeed (if read content had not changed its location), fail with a 'bad local header' exception or even fail silently and return incorrect data. To avoid the issue, we explicitly uninstall the previously installed setuptools distribution before installing its new version. """ if env.sys_version_info[:2] != (2, 5): return # only Python 2.5.x affected by this if not env.setuptools_zipped_egg: return # setuptools not pre-installed as a zipped egg pv_new = parse_version(ez_setup.setuptools_version()) if pv_new != parse_version(env.setuptools_version): return # issue avoided since zipped egg archive names will not match fixed_version = utility.lowest_version_string_with_prefix("3.5.2") if pv_new >= parse_version(fixed_version): return # issue fixed in setuptools # We could check for pip and use it for a cleaner setuptools uninstall if # available, but YAGNI since only Python 2.5.x environments are affected by # the zipped egg upgrade issue. os.remove(env.setuptools_zipped_egg) def _reuse_pre_installed_setuptools(env, installer): """ Return whether a pre-installed setuptools distribution should be reused. """ if not env.setuptools_version: return # no prior setuptools ==> no reuse reuse_old = config.reuse_old_setuptools reuse_best = config.reuse_best_setuptools reuse_future = config.reuse_future_setuptools reuse_comment = None if reuse_old or reuse_best or reuse_future: pv_old = parse_version(env.setuptools_version) pv_new = parse_version(installer.setuptools_version()) if pv_old < pv_new: if reuse_old: reuse_comment = "%s+ recommended" % ( installer.setuptools_version(),) elif pv_old > pv_new: if reuse_future: reuse_comment = "%s+ required" % ( installer.setuptools_version(),) elif reuse_best: reuse_comment = "" if reuse_comment is None: return # reuse not allowed by configuration if reuse_comment: reuse_comment = " (%s)" % (reuse_comment,) print("Reusing pre-installed setuptools %s distribution%s." % ( env.setuptools_version, reuse_comment)) return True # reusing pre-installed setuptools # --------------------------- # Processing pip installation # --------------------------- def calculate_pip_requirements(env_version_info): # pip releases supported on older Python versions: # * Python 2.4.x - pip 1.1. # * Python 2.5.x - pip 1.3.1. pip_version = None if env_version_info < (2, 5): pip_version = "1.1" elif env_version_info < (2, 6): pip_version = "1.3.1" requirement_spec = utility.requirement_spec requirements = [requirement_spec("pip", pip_version)] # Although pip claims to be compatible with Python 3.0 & 3.1 it does not # seem to work correctly from within such clean Python environments. # * Tested using pip 1.5.4 & Python 3.1.3. # * pip can be installed using Python 3.1.3 ('py313 -m easy_install pip') # but attempting to use it or even just import its pip Python module # fails. # * The problem is caused by a bug in pip's backward compatibility # support implementation, but can be worked around by installing the # backports.ssl_match_hostname package from PyPI. if (3,) <= env_version_info < (3, 2): requirements.append(requirement_spec("backports.ssl_match_hostname")) return requirements def download_pip(env, requirements): """Download pip and its requirements using setuptools.""" if config.installation_cache_folder() is None: raise EnvironmentSetupError("Local installation cache folder not " "defined but required for downloading a pip installation.") # Installation cache folder needs to be explicitly created for setuptools # to be able to copy its downloaded installation files into it. Seen using # Python 2.4.4 & setuptools 1.4. _create_installation_cache_folder_if_needed() try: env.execute(["-m", "easy_install", "--zip-ok", "--multi-version", "--always-copy", "--exclude-scripts", "--install-dir", config.installation_cache_folder()] + requirements) zip_eggs_in_folder(config.installation_cache_folder()) except (KeyboardInterrupt, SystemExit): raise except Exception: raise EnvironmentSetupError("pip download failed.") def setuptools_install_options(local_storage_folder): """ Return options to make setuptools use installations from the given folder. No other installation source is allowed. """ if local_storage_folder is None: return [] # setuptools expects its find-links parameter to contain a list of link # sources (either local paths, file: URLs pointing to folders or URLs # pointing to a file containing HTML links) separated by spaces. That means # that, when specifying such items, whether local paths or URLs, they must # not contain spaces. The problem can be worked around by using a local # file URL, since URLs can contain space characters encoded as '%20' (for # more detailed information see below). # # Any URL referencing a folder needs to be specified with a trailing '/' # character in order for setuptools to correctly recognize it as a folder. # # All this has been tested using Python 2.4.3/2.4.4 & setuptools 1.4/1.4.2 # as well as Python 3.4 & setuptools 3.3. # # Supporting paths with spaces - method 1: # ---------------------------------------- # One way would be to prepare a link file and pass an URL referring to that # link file. The link file needs to contain a list of HTML link tags # (), one for every item stored inside the local storage # folder. If a link file references a folder whose name matches the desired # requirement name, it will be searched recursively (as described in method # 2 below). # # Note that in order for setuptools to recognize a local link file URL # correctly, the file needs to be named with the '.html' extension. That # will cause the underlying urllib2.open() operation to return the link # file's content type as 'text/html' which is required for setuptools to # recognize a valid link file. # # Supporting paths with spaces - method 2: # ---------------------------------------- # Another possible way is to use an URL referring to the local storage # folder directly. This will cause setuptools to prepare and use a link # file internally - with its content read from a 'index.html' file located # in the given local storage folder, if it exists, or constructed so it # contains HTML links to all top-level local storage folder items, as # described for method 1 above. if " " in local_storage_folder: find_links_param = utility.path_to_URL(local_storage_folder) if find_links_param[-1] != "/": find_links_param += "/" else: find_links_param = local_storage_folder return ["-f", find_links_param, "--allow-hosts=None"] def install_pip(env, requirements): """Install pip and its requirements using setuptools.""" try: installation_source_folder = config.installation_cache_folder() options = setuptools_install_options(installation_source_folder) if installation_source_folder is not None: zip_eggs_in_folder(installation_source_folder) env.execute(["-m", "easy_install"] + options + requirements) except (KeyboardInterrupt, SystemExit): raise except Exception: raise EnvironmentSetupError("pip installation failed.") def process_pip(env, actions): download = "download pip installation" in actions install = "run pip installation" in actions if not download and not install: return requirements = calculate_pip_requirements(env.sys_version_info) if download: download_pip(env, requirements) if install: install_pip(env, requirements) # ---------------------------------- # Processing pip based installations # ---------------------------------- def pip_invocation_arguments(env_version_info): """ Returns Python arguments for invoking pip with a specific Python version. Running pip based installations on Python prior to 2.7. * pip based installations may be run using: python -c "import pip;pip.main()" install in addition to the regular command: python -m pip install * The '-m' option can not be used with certain Python versions prior to Python 2.7. * Whether this is so also depends on the specific pip version used. * Seems to not work with Python 2.4 and pip 1.1. * Seems to work fine with Python 2.5.4 and pip 1.3.1. * Seems to not work with Python 2.6.6 and pip 1.5.4. """ if (env_version_info < (2, 5)) or ((2, 6) <= env_version_info < (2, 7)): return ["-c", "import pip;pip.main()"] return ["-m", "pip"] def pip_requirements_file(requirements): janitor = None try: os_handle, file_path = tempfile.mkstemp(suffix=".pip-requirements", text=True) requirements_file = os.fdopen(os_handle, "w") try: janitor = utility.FileJanitor(file_path) for line in requirements: requirements_file.write(line) requirements_file.write("\n") finally: requirements_file.close() return file_path, janitor except: if janitor: janitor.clean() raise def prepare_pip_requirements_file_if_needed(requirements): """ Make requirements be passed to pip via a requirements file if needed. We must be careful about how we pass shell operator characters (e.g. '<', '>', '|' or '^') included in our command-line arguments or they might cause problems if run through an intermediate shell interpreter. If our pip requirement specifications contain such characters, we pass them using a separate requirements file. This problem has been encountered on Windows 7 SP1 x64 using Python 2.4.3, 2.4.4 & 2.5.4. """ if utility.any_contains_any(requirements, "<>|()&^"): file_path, janitor = pip_requirements_file(requirements) requirements[:] = ["-r", file_path] return janitor def prepare_pip_requirements(env): requirements = list(itertools.chain( pytest_requirements(env.sys_version_info, env.ctypes_version), six_requirements(env.sys_version_info), virtualenv_requirements(env.sys_version_info))) janitor = prepare_pip_requirements_file_if_needed(requirements) return requirements, janitor def pip_download_cache_options(download_cache_folder): if download_cache_folder is None: return [] return ["--download-cache=" + download_cache_folder] def download_pip_based_installations(env, pip_invocation, requirements, download_cache_folder): """Download requirements for pip based installation.""" if config.installation_cache_folder() is None: raise EnvironmentSetupError("Local installation cache folder not " "defined but required for downloading pip based installations.") # Installation cache folder needs to be explicitly created for pip to be # able to copy its downloaded installation files into it. The same does not # hold for pip's download cache folder which gets created by pip on-demand. # Seen using Python 3.4.0 & pip 1.5.4. _create_installation_cache_folder_if_needed() try: pip_options = ["install", "-d", config.installation_cache_folder(), "--exists-action=i"] pip_options.extend(pip_download_cache_options(download_cache_folder)) # Running pip based installations on Python 2.5. # * Python 2.5 does not come with SSL support enabled by default and # so pip can not use SSL certified downloads from PyPI. # * To work around this either install the # https://pypi.python.org/pypi/ssl package or run pip using the # '--insecure' command-line options. # * Installing the ssl package seems ridden with problems on # Python 2.5 so this workaround has not been tested. if (2, 5) <= env.sys_version_info < (2, 6): # There are some potential cases where we do not need to use # "--insecure", e.g. if the target Python environment already has # the 'ssl' module installed. However, detecting whether this is so # does not seem to be worth the effort. The only way to detect # whether secure download is supported would be to scan the target # environment for this information, e.g. setuptools has this # information in its pip.backwardcompat.ssl variable - if it is # None, the necessary SSL support is not available. But then we # would have to be careful: # - not to run the scan if we already know this information from # some previous scan # - to track all actions that could have invalidated our previous # scan results, etc. # It just does not seem to be worth the hassle so for now - YAGNI. pip_options.append("--insecure") env.execute(pip_invocation + pip_options + requirements) except (KeyboardInterrupt, SystemExit): raise except Exception: raise EnvironmentSetupError("pip based download failed.") def run_pip_based_installations(env, pip_invocation, requirements, download_cache_folder): # 'pip' download caching system usage notes: # 1. When not installing from our own local installation storage folder, we # can still use pip's internal download caching system. # 2. We must not enable pip's internal download caching system when # installing from our own local installation storage folder. In that # case, pip attempts to populate its cache from our local installation # folder, but that logic fails when our folder contains a wheel (.whl) # distribution. More precisely, it fails attempting to store the wheel # distribution file's content type information. Tested using Python # 3.4.0 & pip 1.5.4. try: pip_options = ["install"] if config.installation_cache_folder() is None: pip_options.extend(pip_download_cache_options( download_cache_folder)) else: # pip allows us to identify a local folder containing predownloaded # installation packages using its '-f' command-line option taking # an URL parameter. However, it does not require the URL to be # URL-quoted and it does not even seem to recognize URLs containing # %xx escaped characters. Tested using an installation cache folder # path containing spaces with Python 3.4.0 & pip 1.5.4. installation_cache_folder_URL = utility.path_to_URL( config.installation_cache_folder(), escape=False) pip_options.extend(["-f", installation_cache_folder_URL, "--no-index"]) env.execute(pip_invocation + pip_options + requirements) except (KeyboardInterrupt, SystemExit): raise except Exception: raise EnvironmentSetupError("pip based installation failed.") def post_pip_based_installation_fixups(env): """Apply simple post-installation fixes for pip installed packages.""" if env.sys_version_info[:2] == (3, 1): from suds_devel.patch_pytest_on_python_31 import patch patch(env) def process_pip_based_installations(env, actions, download_cache_folder): download = "download pip based installations" in actions install = "run pip based installations" in actions if not download and not install: return pip_invocation = pip_invocation_arguments(env.sys_version_info) janitor = None try: requirements, janitor = prepare_pip_requirements(env) if download: download_pip_based_installations(env, pip_invocation, requirements, download_cache_folder) if install: run_pip_based_installations(env, pip_invocation, requirements, download_cache_folder) post_pip_based_installation_fixups(env) finally: if janitor: janitor.clean() # ------------------------------ # Processing Python environments # ------------------------------ def enabled_actions_for_env(env): """Returns actions to perform when processing the given environment.""" def enabled(config_value, required): if config_value is Config.TriBool.No: return False if config_value is Config.TriBool.Yes: return True assert config_value is Config.TriBool.IfNeeded return bool(required) # Some old Python versions do not support HTTPS downloads and therefore can # not download installation packages from PyPI. To run setuptools or pip # based installations on such Python versions, all the required # installation packages need to be downloaded locally first using a # compatible Python version (e.g. Python 2.4.4 for Python 2.4.3) and then # installed locally. download_supported = not ((2, 4, 3) <= env.sys_version_info < (2, 4, 4)) local_install = config.installation_cache_folder() is not None actions = set() pip_required = False run_pip_based_installations = enabled(config.install_environments, True) if run_pip_based_installations: actions.add("run pip based installations") pip_required = True if download_supported and enabled(config.download_installations, local_install and run_pip_based_installations): actions.add("download pip based installations") pip_required = True setuptools_required = False run_pip_installation = enabled(config.install_environments, pip_required) if run_pip_installation: actions.add("run pip installation") setuptools_required = True if download_supported and enabled(config.download_installations, local_install and run_pip_installation): actions.add("download pip installation") setuptools_required = True if enabled(config.setup_setuptools, setuptools_required): actions.add("setup setuptools") return actions def print_environment_processing_title(env): title_length = 73 print("-" * title_length) title = "--- %s - Python %s " % (env.name(), env.python_version) title += "-" * max(0, title_length - len(title)) print(title) print("-" * title_length) def process_Python_environment(env): actions = enabled_actions_for_env(env) if not actions: return print_environment_processing_title(env) process_setuptools(env, actions) process_pip(env, actions) process_pip_based_installations(env, actions, config.pip_download_cache_folder()) def process_python_environments(python_environments): for env in python_environments: try: process_Python_environment(env) except EnvironmentSetupError: utility.report_error(sys.exc_info()[1]) def main(): try: _report_startup_information() _prepare_configuration() _report_configuration() python_environments = scan_python_environments() except BadConfiguration: utility.report_error(sys.exc_info()[1]) return -2 process_python_environments(python_environments) return 0 if __name__ == "__main__": sys.exit(main()) suds-1.1.2/tools/suds_devel/000077500000000000000000000000001425611400200157615ustar00rootroot00000000000000suds-1.1.2/tools/suds_devel/__init__.py000066400000000000000000000017271425611400200201010ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Internal implementation package for different project development utility scripts. """ suds-1.1.2/tools/suds_devel/configuration.py000066400000000000000000000124641425611400200212110ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Basic configuration support shared in different development utility scripts. """ import os.path import sys # Must not use the six Python 2/3 compatibility package from here as this # module gets used from the script for setting up basic development # environments, and that script needs to be runnable even before the six # package has been installed. if sys.version_info < (3,): import ConfigParser as configparser else: import configparser from suds_devel.environment import Environment import suds_devel.utility as utility class BadConfiguration(Exception): def __init__(self, message): Exception.__init__(self, message) class Config(object): # Typed option values. BOOLEAN_TRUE = ('1', 'yes', 'true', 'on', '+') BOOLEAN_FALSE = ('0', 'no', 'false', 'off', '-') IF_NEEDED = ('maybe', '?', 'ifneeded', 'if needed', 'asneeded', 'as needed', 'ondemand', 'on demand') class TriBool: Yes = object() IfNeeded = object() No = object() def __init__(self, script, project_folder, ini_file): """ Initialize new script configuration. External configuration parameters may be specified relative to the following folders: * script - relative to the current working folder * project_folder - relative to the script folder * ini_file - relative to the project folder """ self.__init_script_folder(script) self.__init_project_folder(project_folder) self.__init_ini_file(ini_file) self.__init_reader() def _get_bool(self, section, option): x = self._reader.get(section, option).lower() if x in self.BOOLEAN_TRUE: return True if x in self.BOOLEAN_FALSE: return False raise BadConfiguration("Option '%s.%s' must be a boolean value." % ( section, option)) def _get_tribool(self, section, option): x = self._reader.get(section, option).lower() if x in self.BOOLEAN_TRUE: return Config.TriBool.Yes if x in self.BOOLEAN_FALSE: return Config.TriBool.No if x in self.IF_NEEDED: return Config.TriBool.IfNeeded raise BadConfiguration("Option '%s.%s' must be Yes, No or IfNeeded." % (section, option)) def _read_environment_configuration(self): section_prefix = "env:" command_option = "command" self.python_environments = [] for section in self._reader.sections(): if section.lower().startswith(section_prefix): name = section[len(section_prefix):] command = self._reader.get(section, command_option) if not command: raise BadConfiguration("'%s.%s' environment command " "configuration option must not be empty." % (section, command_option)) self.python_environments.append(Environment(name, command)) def __init_ini_file(self, ini_file): self.ini_file = os.path.join(self.project_folder, ini_file) self.ini_file = os.path.normpath(self.ini_file) if not os.path.isfile(self.ini_file): raise BadConfiguration("Missing configuration file '%s'." % ( self.ini_file,)) def __init_project_folder(self, project_folder): p = os.path.normpath(os.path.join(self.script_folder, project_folder)) if not os.path.isdir(p): raise BadConfiguration("Could not find project folder '%s'." % p) self.project_folder = p def __init_reader(self): try: f = open(self.ini_file, "r") except (KeyboardInterrupt, SystemExit): raise except Exception: raise BadConfiguration("Can not access configuration file '%s' - " "%s." % (self.ini_file, sys.exc_info()[1])) try: print("Reading configuration file '%s'..." % (self.ini_file,)) self._reader = configparser.ConfigParser() self._reader.readfp(f) finally: f.close() def __init_script_folder(self, script): self.script_folder = utility.script_folder(script) if not self.script_folder: raise BadConfiguration("Could not determine script folder.") suds-1.1.2/tools/suds_devel/egg.py000066400000000000000000000123511425611400200170770ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Manipulating egg distributions. """ import os import os.path from suds_devel.zip import zip_folder_content def zip_eggs_in_folder(folder): """ Make all egg distributions in the given folder be stored as egg files. In case setuptools downloads one of its target packages as an unzipped egg folder (e.g. if installed from an already installed unzipped egg), we need to zip it ourselves. This is because there seems to be no way to make setuptools perform an installation from a local unzipped egg folder. Specifying either that folder or its parent folder as a setuptools find-links URL just makes the folder be treated as a regular non-egg folder. """ eggs = _detect_eggs_in_folder(folder) for egg in eggs: egg.normalize() # Egg distribution related file & folder name extensions. _egg_ext = os.extsep + "egg" _zip_ext = _egg_ext + os.extsep + "zip" class _Egg: """ Represents a single egg distribution. Helps track & manage formats the distribution is stored in: - zipped egg file with .egg extension - zipped egg file with .egg.zip extension - unzipped egg folder """ # Indicators whether the egg distribution has a '.egg' file or folder. NONE = object() FILE = object() FOLDER = object() def __init__(self, path, egg, zip): assert egg in (_Egg.NONE, _Egg.FILE, _Egg.FOLDER) assert zip.__class__ is bool assert zip or egg is not _Egg.NONE self.__path = path self.__egg = egg self.__zip = zip def has_egg_file(self): return self.__egg is _Egg.FILE def has_egg_folder(self): return self.__egg is _Egg.FOLDER def has_zip(self): return self.__zip def normalize(self): """ Makes sure this egg distribution is stored only as an egg file. The egg file will be created from another existing distribution format if needed. """ if self.has_egg_file(): if self.has_zip(): self.__remove_zip() else: if self.has_egg_folder(): if not self.has_zip(): self.__zip_egg_folder() self.__remove_egg_folder() self.__rename_zip_to_egg() def set_egg(self, egg): assert egg in (_Egg.FILE, _Egg.FOLDER) assert self.__egg is _Egg.NONE self.__egg = egg def set_zip(self): assert not self.__zip self.__zip = True def __path_egg(self): return self.__path + _egg_ext def __path_zip(self): return self.__path + _zip_ext def __remove_egg_folder(self): assert self.has_egg_folder() import shutil shutil.rmtree(self.__path_egg()) self.__egg = _Egg.NONE def __remove_zip(self): assert self.has_zip() os.remove(self.__path_zip()) self.__zip = False def __rename_zip_to_egg(self): assert self.has_zip() assert not self.has_egg_file() assert not self.has_egg_folder() os.rename(self.__path_zip(), self.__path_egg()) self.__egg = _Egg.FILE self.__zip = False def __zip_egg_folder(self): assert self.has_egg_folder() assert not self.has_zip() zip_folder_content(self.__path_egg(), self.__path_zip()) self.__zip = True def _detect_eggs_in_folder(folder): """ Detect egg distributions located in the given folder. Only direct folder content is considered and subfolders are not searched recursively. """ eggs = {} for x in os.listdir(folder): zip = x.endswith(_zip_ext) if zip: root = x[:-len(_zip_ext)] egg = _Egg.NONE elif x.endswith(_egg_ext): root = x[:-len(_egg_ext)] if os.path.isdir(os.path.join(folder, x)): egg = _Egg.FOLDER else: egg = _Egg.FILE else: continue try: info = eggs[root] except KeyError: eggs[root] = _Egg(os.path.join(folder, root), egg, zip) else: if egg is not _Egg.NONE: info.set_egg(egg) if zip: info.set_zip() return eggs.values() suds-1.1.2/tools/suds_devel/environment.py000066400000000000000000000405631425611400200207070ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Class representing a single Python environment. Includes support for: - fetching information about a specific Python environment - executing a command in a specific Python environment """ import sys have_subprocess_devnull = sys.version_info >= (3, 3) if not have_subprocess_devnull: import os import subprocess class BadEnvironment(Exception): """ Problem occurred while scanning a Python environment. The problem may be either a technical one in the scanning process itself, or it may be a problem with something detected about the Python environment in question, e.g. it might be using an incompatible Python version. Specifying the environment scan result information when constructing this exception is optional and may be added to an exception later on using the set_raw_scan_results() method. """ def __init__(self, message, out=None, err=None, exit_code=None): Exception.__init__(self, message) self.message = message self.out = out self.err = err self.exit_code = exit_code def raw_scan_results(self): return self.out, self.err, self.exit_code def set_raw_scan_results(self, out, err, exit_code): assert self.out is None assert self.err is None assert self.exit_code is None self.out = out self.err = err self.exit_code = exit_code class _UndefinedParameter: """Internal class used to indicate undefined parameter values.""" pass class Environment: """ Represents a single Python environment. Allows running commands using the environment's Python interpreter. Allows running an initial Python environment scan to collect information about it. Note that most of the information about the environment will not be available until the initial environment scan is run. """ def __init__(self, name, command): self.__name = name self.__command = command self.initial_scan_completed = False def command(self): return self.__command def description(self): return "%s on %s" % (self.sys_version, self.sys_platform) def execute(self, args=[], input=None, capture_output=False, cwd=_UndefinedParameter): stdin = subprocess.PIPE close_stdin = False if input is None: if have_subprocess_devnull: stdin = subprocess.DEVNULL else: stdin = open(os.devnull, "r") close_stdin = True try: kwargs = {} kwargs["stdin"] = stdin if capture_output: kwargs["stdout"] = subprocess.PIPE kwargs["stderr"] = subprocess.PIPE if cwd is not _UndefinedParameter: kwargs["cwd"] = cwd kwargs["universal_newlines"] = True popen = subprocess.Popen([self.command()] + args, **kwargs) out, err = popen.communicate(input) finally: if close_stdin: stdin.close() return out, err, popen.returncode def name(self): return self.__name def run_initial_scan(self): self.initial_scan_completed = False scanner = self.__initial_scanner() try: scan_results = scanner.scan(self) self.__collect_scanned_values(scan_results) self.__parse_scanned_version_info() self.initial_scan_completed = True self.__check_python_version() except BadEnvironment: sys.exc_info()[1].set_raw_scan_results(*scanner.raw_scan_results()) raise return scanner.raw_scan_results() def __check_python_version(self): if self.sys_version_info < (2, 4): raise BadEnvironment("Unsupported Python version (%s, %s-bit)" % (self.python_version, self.pointer_size_in_bits)) def __collect_scanned_values(self, values): v = values # System. self.platform_architecture = v["platform.architecture"] self.pointer_size_in_bits = v["pointer size in bits"] self.sys_path = v["sys.path"] self.sys_version = v["sys.version"] self.sys_version_info_formatted = v["sys.version_info (formatted)"] self.sys_version_info_raw = v["sys.version_info (raw)"] self.sys_platform = v["sys.platform"] self.sys_executable = v["sys.executable"] # Packages. self.ctypes_version = v["ctypes version"] self.pip_version = v["pip version"] self.pytest_version = v["pytest version"] self.setuptools_version = v["setuptools version"] self.virtualenv_version = v["virtualenv version"] # Extra package info. self.setuptools_zipped_egg = v["setuptools zipped egg"] def __construct_python_version(self): """ Construct a setuptools compatible Python version string. Constructed based on the environment's reported sys.version_info. """ major, minor, micro, release_level, serial = self.sys_version_info assert release_level in ("alfa", "beta", "candidate", "final") assert release_level != "final" or serial == 0 parts = [str(major), ".", str(minor), ".", str(micro)] if release_level != "final": parts.append(release_level[0]) parts.append(str(serial)) self.python_version = "".join(parts) @staticmethod def __initial_scanner(): s = EnvironmentScanner() s.add_import("os.path") s.add_import("platform") s.add_import("struct") s.add_import("sys") s.add_function("""\ def version_info_string(): major, minor, micro, release_level, serial = sys.version_info if not (isinstance(major, int) and isinstance(minor, int) and isinstance(micro, int) and isinstance(release_level, str) and isinstance(serial, int)) or "," in release_level: return "" # At least Python versions 2.7.6 & 3.1.3 require an explicit tuple() cast. return "%d,%d,%d,%s,%d" % tuple(sys.version_info) """) s.add_function("""\ def setuptools_zipped_egg(): try: from pkg_resources import ( DistributionNotFound, EGG_DIST, get_distribution) except ImportError: return Skip try: d = get_distribution("setuptools") except DistributionNotFound: return Skip if d.precedence != EGG_DIST or not os.path.isfile(d.location): return Skip # file = zipped egg; folder = unzipped egg return d.location """) s.add_field("platform.architecture", "platform.architecture()") s.add_field("pointer size in bits", '8 * struct.calcsize("P")') s.add_field("sys.executable") s.add_field("sys.path") s.add_field("sys.platform") s.add_field("sys.version") s.add_field("sys.version_info (formatted)", "version_info_string()") s.add_field("sys.version_info (raw)", "sys.version_info") s.add_package_version_field("ctypes", default=None) s.add_package_version_field("pip", default=None) s.add_package_version_field("pytest", default=None) s.add_package_version_field("setuptools", default=None) s.add_package_version_field("virtualenv", default=None) s.add_field("setuptools zipped egg", "setuptools_zipped_egg()", None) return s def __parse_scanned_version_info(self): """Parses the environment's formatted version info string.""" string = self.sys_version_info_formatted try: major, minor, micro, release_level, serial = string.split(",") if (release_level in ("alfa", "beta", "candidate", "final") and (release_level != "final" or serial == "0") and major.isdigit() and # --- --- --- --- --- --- --- --- --- minor.isdigit() and # Explicit isdigit() checks to detect micro.isdigit() and # leading/trailing whitespace. serial.isdigit()): # --- --- --- --- --- --- --- --- --- self.sys_version_info = (int(major), int(minor), int(micro), release_level, int(serial)) self.__construct_python_version() return except (KeyboardInterrupt, SystemExit): raise except Exception: pass raise BadEnvironment("Unsupported Python version (%s)" % (string,)) class EnvironmentScanner: """ Allows scanning a given Python environment for specific information. Runs a scanner script in the given Python environment, crafted based on the information we wish to find out about the environment, and then collects and returns the requested information. """ # Explicitly specified scan output start & finish markers allow us to # safely ignore extra output that might be added by the environment's # Python interpreter startup. This can be useful, for instance, if you want # to debug this script by having the Python interpreter startup script # output the exact calls made to the Python interpreter. SCAN_START_MARKER = "--- SCAN START ---" SCAN_FINISH_MARKER = "--- SCAN FINISH ---" __scanner_script__startup = """\ class Skip: pass def print_field(id, value): if value is not Skip: print("%s: %s" % (id, value)) """ # If available, setuptools can give us more detailed version information # read from the package's meta-data than simply reading it from its # __version__ attribute. For example, in case of setuptools it can tell us # that we are dealing with version '3.7dev' while __version__ would tell us # just '3.7'. Also, some older packages, such as pip 1.1, may not have a # __version__ attribute set in their main module at all. __scanner_script__package_version_scanner = """\ def package_version(package_name): try: package = __import__(package_name, {}, {}, ("__version__",)) except (KeyboardInterrupt, SystemExit): raise except Exception: return Skip # No package - no version. try: version = package.__version__ except AttributeError: version = "unknown" try: from pkg_resources import (DistributionNotFound, get_distribution) except ImportError: return version try: return get_distribution(package_name).version except DistributionNotFound: return version """ def __init__(self): self.__out = None self.__err = None self.__exit_code = None self.__scan_for_version_info = False self.__extra_script_functions = [] self.__extra_script_imports = [] self.__fields = [] def add_field(self, id, getter=None, default=_UndefinedParameter): if getter is None: getter = id self.__add_field(id, "print_field(%r, %s)" % (id, getter), default) def add_function(self, code): self.__extra_script_functions.append(code) def add_import(self, module_name): self.__extra_script_imports.append(module_name) def add_package_version_field(self, package_name, default=_UndefinedParameter): self.__scan_for_version_info = True field_id = "%s version" % (package_name,) field_getter = "print_field(%r, package_version(%r))" % ( field_id, package_name) self.__add_field(field_id, field_getter, default) def raw_scan_results(self): return self.__out, self.__err, self.__exit_code def scan(self, environment): self.__scan(environment) raw_data = self.__parse_raw_scanner_output() return self.__map_raw_data_to_expected_fields(raw_data) def __add_field(self, id, getter, default): assert id not in [x[0] for x in self.__fields] self.__fields.append((id, getter, default)) @staticmethod def __extract_value(raw_data, id, default): if default is not _UndefinedParameter: return raw_data.pop(id, default) try: return raw_data.pop(id) except KeyError: raise BadEnvironment("Missing scan output record (%s)" % ( sys.exc_info()[1],)) def __map_raw_data_to_expected_fields(self, raw_data): scanner_results = {} for id, getter, default in self.__fields: assert id not in scanner_results scanner_results[id] = self.__extract_value(raw_data, id, default) if raw_data: raise BadEnvironment("Extra scan output records (%s)" % ( ",".join(raw_data.keys()),)) return scanner_results def __parse_raw_scanner_output(self): assert self.__scanned() result = {} in_scanner_output = False for line in self.__out.split("\n"): if not in_scanner_output: in_scanner_output = line.startswith(self.SCAN_START_MARKER) continue if line.startswith(self.SCAN_FINISH_MARKER): return result split_result = line.split(": ", 1) if len(split_result) != 2: raise BadEnvironment("Error parsing scan output record") key, value = split_result if key in result: raise BadEnvironment("Duplicate scan output record (%s)" % (key,)) result[key] = value if not in_scanner_output: raise BadEnvironment("No valid scan output detected") raise BadEnvironment("Scan output truncated") def __scan(self, environment): assert not self.__scanned() try: self.__out, self.__err, self.__exit_code = environment.execute( input=self.__scanner_script(), capture_output=True) except (KeyboardInterrupt, SystemExit): raise except Exception: e_type, e = sys.exc_info()[:2] try: raise BadEnvironment("%s: %s" % (e_type.__name__, e)) finally: del e # explicitly break circular reference chain in Python 3 if self.__exit_code != 0: raise BadEnvironment("Scan failed (exit code %d)" % (self.__exit_code,)) if self.__err: raise BadEnvironment("Scan failed (error output detected)") if not self.__out: raise BadEnvironment("Scan failed (no output)") def __scanned(self): return self.__exit_code is not None def __scanner_script(self): script_lines = [] if self.__extra_script_imports: for x in self.__extra_script_imports: script_lines.append("import %s" % (x,)) script_lines.append("") script_lines.append(self.__scanner_script__startup) script_lines.append("") if self.__scan_for_version_info: script_lines.append(self.__scanner_script__package_version_scanner) script_lines.extend(self.__extra_script_functions) script_lines.append("") script_lines.append('print("%s")' % (self.SCAN_START_MARKER,)) script_lines.extend(getter for id, getter, default in self.__fields) script_lines.append('print("%s")' % (self.SCAN_FINISH_MARKER,)) script_lines.append("") return "\n".join(script_lines) suds-1.1.2/tools/suds_devel/exception.py000066400000000000000000000024421425611400200203330ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Generic exception classes shared in different development utility modules. """ class EnvironmentSetupError(Exception): """ Signal to move onto setting up the next environment. Raised after the current environment's setup fails. Error message stored in the exception is expected to be reported to the user. """ def __init__(self, message): Exception.__init__(self, message) suds-1.1.2/tools/suds_devel/ez_setup_versioned.py000066400000000000000000000026221425611400200222510ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Managing Python version specific setuptools ez_setup setup modules used in this project. """ import sys def import_module(version_info=sys.version_info): return __import__(module_name(version_info)) def script_name(version_info=sys.version_info): return module_name(version_info) + ".py" def module_name(version_info=sys.version_info): if version_info < (2, 6): # setuptools 1.4.2 - the final supported release on Python 2.4 & 2.5. return "ez_setup_1_4_2" return "ez_setup" suds-1.1.2/tools/suds_devel/parse_version.py000066400000000000000000000102551425611400200212150ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Utility for converting version strings into a directly comparable tuple. Shamelessly stolen from setuptools 3.7 (development version) where this code is located in its pkg_resources.py module. The code has not been modified in any way, including comments and coding style, so that it would be as easy as possible to upgrade it to a newer version if that becomes needed. Having this functionality in our project allows our tool scripts to run even without locally installed setuptools installed, e.g. the script for setting up the base development environments. """ import re component_re = re.compile(r'(\d+ | [a-z]+ | \.| -)', re.VERBOSE) replace = {'pre':'c', 'preview':'c','-':'final-','rc':'c','dev':'@'}.get def _parse_version_parts(s): for part in component_re.split(s): part = replace(part, part) if not part or part=='.': continue if part[:1] in '0123456789': # pad for numeric comparison yield part.zfill(8) else: yield '*'+part # ensure that alpha/beta/candidate are before final yield '*final' def parse_version(s): """Convert a version string to a chronologically-sortable key This is a rough cross between distutils' StrictVersion and LooseVersion; if you give it versions that would work with StrictVersion, then it behaves the same; otherwise it acts like a slightly-smarter LooseVersion. It is *possible* to create pathological version coding schemes that will fool this parser, but they should be very rare in practice. The returned value will be a tuple of strings. Numeric portions of the version are padded to 8 digits so they will compare numerically, but without relying on how numbers compare relative to strings. Dots are dropped, but dashes are retained. Trailing zeros between alpha segments or dashes are suppressed, so that e.g. "2.4.0" is considered the same as "2.4". Alphanumeric parts are lower-cased. The algorithm assumes that strings like "-" and any alpha string that alphabetically follows "final" represents a "patch level". So, "2.4-1" is assumed to be a branch or patch of "2.4", and therefore "2.4.1" is considered newer than "2.4-1", which in turn is newer than "2.4". Strings like "a", "b", "c", "alpha", "beta", "candidate" and so on (that come before "final" alphabetically) are assumed to be pre-release versions, so that the version "2.4" is considered newer than "2.4a1". Finally, to handle miscellaneous cases, the strings "pre", "preview", and "rc" are treated as if they were "c", i.e. as though they were release candidates, and therefore are not as new as a version string that does not contain them, and "dev" is replaced with an '@' so that it sorts lower than than any other pre-release tag. """ parts = [] for part in _parse_version_parts(s.lower()): if part.startswith('*'): # remove '-' before a prerelease tag if part<'*final': while parts and parts[-1]=='*final-': parts.pop() # remove trailing zeros from each series of numeric parts while parts and parts[-1]=='00000000': parts.pop() parts.append(part) return tuple(parts) suds-1.1.2/tools/suds_devel/patch_pytest_on_python_31.py000066400000000000000000000122561425611400200234500ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Support for patching an existing pytest installation to make it work correctly in a Python 3.1 environment. pytest versions prior to its 2.6 release do not support Python 3.1 out of the box, but can be patched to do so. Basic gist of this patch is to replace a single call to the builtin function 'callable()' in pytest's _pytest/runner.py module with a call to 'py.builtin.callable()'. The patch has been tested to make pytest 2.5.2 work correctly with Python 3.1 and is based on code found in pytest pull request #168 & commit 04c4997da865344f2ebb8569c73c51c57cd4ba05. """ import os import os.path import re import sys from suds_devel.environment import EnvironmentScanner from suds_devel.exception import EnvironmentSetupError from suds_devel.parse_version import parse_version def patch(env): assert env.sys_version_info[:2] == (3, 1) pytest_location, pytest_version = _scan(env) if parse_version(pytest_version) >= parse_version("2.6.0"): return print("Patching installed pytest package...") file_path = os.path.join(pytest_location, "_pytest", "runner.py") file_temp_path = _file_temp_path(file_path) try: try: prepatched_count, patched_count = _patch(file_path, file_temp_path, unpatched_regex="(.*\s)callable([(].*)$", patched_regex="(.*\s)py[.]builtin[.]callable([(].*)", patch_pattern="%spy.builtin.callable%s") if (prepatched_count, patched_count) not in ((1, 0), (0, 1)): _error(file_path, "file content not recognized") if prepatched_count: print("WARNING: pytest already patched") else: os.remove(file_path) os.rename(file_temp_path, file_path) finally: _try_remove_file(file_temp_path) except (EnvironmentSetupError, KeyboardInterrupt, SystemExit): raise except Exception: _error(file_path, str(sys.exc_info()[1])) def _error(file_path, message): raise EnvironmentSetupError("can not patch pytest module '%s' - %s" % ( file_path, message)) def _file_temp_path(file_path): for i in range(100): temp_path = "%s.patching.%d.tmp" % (file_path, i) if not os.path.exists(temp_path): return temp_path _error(file_path, "can not find available temp file name") def _patch(source, dest, unpatched_regex, patched_regex, patch_pattern): """ Patch given source file into the given destination file. Patching is done line by line. Given un-patched and patched line detection regular expressions are expected never to match the same line. Does not modify the source file in any way. Returns the number of detected pre-patched lines, and a number of newly patched lines. """ prepatched_count = 0 patched_count = 0 unpatched_matcher = re.compile(unpatched_regex, re.DOTALL) patched_matcher = re.compile(patched_regex) f_in = None f_out = None try: f_in = open(source, "r") f_out = open(dest, mode="w") for line in f_in: match = unpatched_matcher.match(line) if match: assert not patched_matcher.match(line) patched_count += 1 line = patch_pattern % match.groups() assert patched_matcher.match(line) elif patched_matcher.match(line): prepatched_count += 1 f_out.write(line) finally: if f_in: f_in.close() if f_out: f_out.close() return prepatched_count, patched_count def _scan(env): """Scan the given Python environment's pytest installation.""" s = EnvironmentScanner() s.add_function("""\ def get_pytest_location(): from pkg_resources import get_distribution return get_distribution("pytest").location """) s.add_field("pytest location", "get_pytest_location()") s.add_package_version_field("pytest", default="") scan_results = s.scan(env) return scan_results["pytest location"], scan_results["pytest version"] def _try_remove_file(path): try: os.remove(path) except (KeyboardInterrupt, SystemExit): raise except Exception: pass suds-1.1.2/tools/suds_devel/requirements.py000066400000000000000000000313571425611400200210670ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Package requirements for this project. Extracted here so they can be reused between the project's setup and different development utility scripts. Python 2.4 pytest compatibility notes: -------------------------------------- pytest versions prior to 2.4.0 may be installed but will fail at runtime when running our test suite, as they can not parse all of the pytest constructs used in this project, e.g. skipif expressions not given as strings. Versions 2.4.2 and later can not be installed at all. pytest 2.4.0 release formally broke compatibility with Python releases prior to 2.5 and the last officially supported pytest version on Python 2.4 platforms is 2.3.5. Our tests can still be run using a Python 2.4.x environment if the following package versions are installed into it: - pytest - not older than 2.4.0 nor equal to or newer than 2.4.2 - py - older than 1.4.16 (version 1.4.16 may be installed but will cause pytest to fail when running our test suite). Listed package versions can be installed together using a pip command like: install pytest>=2.4.0,<2.4.2 py<1.4.16 Listed pytest versions specify py version 1.4.16+ as their requirement but work well enough for us with this older py release. Note though that due to pytest not having its requirements formally satisfied, some operations related to it may fail unexpectedly. For example, running setuptools installed 'py.test' startup scripts will fail, as they explicitly check that all the formally specified pytest requirements have been met, but pytest can still be started using 'py24 -m pytest'. See the project's Python compatibility related hacking docs for more detailed information. Python 2.5 pytest compatibility notes: -------------------------------------- pytest 2.6.1 release started using the 'with' statement and so broke compatibility with Python 2.5. py 1.4.24 release started using the 'with' statement and so broke compatibility with Python 2.5. """ import sys from suds_devel.parse_version import parse_version from suds_devel.utility import (lowest_version_string_with_prefix, requirement_spec) class _Unspecified: pass _first_unsupported_py_version_on_Python_24 = ( lowest_version_string_with_prefix("1.4.16")) _first_unsupported_py_version_on_Python_25 = ( lowest_version_string_with_prefix("1.4.24")) # pytest versions prior to 2.4.0 do not support non-string 'skipif' # expressions. _first_supported_pytest_version = "2.4.0" _first_unsupported_pytest_version_on_Python_24 = ( lowest_version_string_with_prefix("2.4.2")) # pytest version 2.6.0 actually supports Python 2.5 but has some internal # issues causing it to break our our tests, while version 2.6.1 fails to # install on Python 2.5 all together. _first_unsupported_pytest_version_on_Python_25 = ( lowest_version_string_with_prefix("2.6.0")) def check_Python24_pytest_requirements(): """ Check pytest requirements in the current Python 2.4.x environment. Installing pytest into a Python 2.4.x environment requires specific py & pytest package versions. This function checks whether the environment has such compatible Python environments installed. Returns a 2-tuple (have_pytest, have_py) indicating whether valid pytest & py library packages have been detected in the current Python 2.4.x environment. If the pytest package has not been detected, the py library package will not be checked and the have_py value will be set to None. See the module docstring for more detailed information. """ assert sys.version_info[:2] == (2, 4) try: from pytest import __version__ as pytest_version except ImportError: return False, None # no pytest pv_from = parse_version(_first_supported_pytest_version) pv_to = parse_version(_first_unsupported_pytest_version_on_Python_24) if not (pv_from <= parse_version(pytest_version) < pv_to): return False, None # incompatible pytest version try: from py import __version__ as py_version except ImportError: return True, False # no py library package pv_unsupported = parse_version(_first_unsupported_py_version_on_Python_24) if parse_version(py_version) >= pv_unsupported: return True, False # incompatible py library package version return True, True def pytest_requirements(version_info=None, ctypes_version=_Unspecified): """ Generate Python version specific pytest package requirements. The requirements are returned as setuptools/pip compatible requirement specification strings. As a slight optimization, specify no Python version information to indicate that the requirements are being listed for the current Python environment. Missing ctypes installation should be indicated by setting ctypes_version parameter to None, while not specifying it indicates that no ctypes version information is provided. """ current_environment = version_info is None if current_environment: version_info = sys.version_info pytest_version = None if version_info < (2, 5): pytest_version = ( (">=", _first_supported_pytest_version), ("<", _first_unsupported_pytest_version_on_Python_24)) yield requirement_spec("py", ("<", _first_unsupported_py_version_on_Python_24)) #IDEA: In this case we could run the pytest installation separately # from all the other pip based installations and have it not install # pytest scripts. Since in general there is no 'setup.py install' or # 'pip' command-line argument that can say 'do not install scripts', # this would most likely need to use a pip command-line option like # '--install-option="--install-scripts=..."' to make the scripts be # installed into a temporary folder and then remove that folder after # the installation. An alternative would be to use easy_install which # does support the --exclude-scripts command-line option. N.B. This # could work for the project's Python environment setup scripts but not # when installing the the project's using its setup script as that # script expects to set up all the required packages itself. # pytest on Windows depends on the colorama package, and that package has # several accidental backward compatibility issues we have to work around # when using Python 2.5. elif version_info < (2, 6): if sys.platform == "win32": # colorama releases [0.1.11 - 0.3.2> do not work unless the ctypes # module is available, but that module is not included in 64-bit # CPython distributions (tested using Python 2.5.4). Some of those # versions fail to install, while others only fail at run-time. # Pull request https://github.com/tartley/colorama/pull/4 resolves # this issue for colorama release 0.3.2. if ctypes_version is _Unspecified: assert current_environment try: from ctypes import __version__ as ctypes_version except ImportError: ctypes_version = None if ctypes_version is None: # We could try to install an external 'ctypes' package from # PyPI here, but that would require an old C++ compiler and so # would not be highly likely to work in any concurrent # development environment. # #TODO: When using colorama releases older than 0.1.11, you # might get atexit() errors on process shutdown after running # the project's 'setup.py test' command and having it # automatically install the colorama package in the process. # The error itself is benign and there are no other effects to # it other than the error message getting displayed. The whole # issue is caused by colorama's atexit handler getting called # multiple times due to some internal setuptools module loading # and unloading. We found no easy workaround to this so update # this code to use the colorama package version 0.3.2+ as soon # as it gets released. When this is done, also remove a related # setup.py comment. v_bad_low = lowest_version_string_with_prefix("0.1.11") v_bad_high = "0.3.2" version_spec = ("<", v_bad_low), (">=", v_bad_high) else: # colorama 0.3.1 release accidentally uses the 'with' keyword # without a corresponding __future__ import in its setup.py # script. version_spec = ("!=", "0.3.1"), yield requirement_spec("colorama", *version_spec) yield requirement_spec("py", ("<", _first_unsupported_py_version_on_Python_25)) pytest_version = ( (">=", _first_supported_pytest_version), ("<", _first_unsupported_pytest_version_on_Python_25)) # Python 3.0 & 3.1 stdlib does not include the argparse module which pytest # requires, even though it does not list it explicitly among its # requirements. Python 3.x series introduced the argparse module in its 3.2 # release so it needs to be installed manually in 3.0.x & 3.1.x releases. # Tested that pytest 2.5.2+ requires py 1.4.20+ which in turn requires the # argparse module but does not specify this dependency explicitly. elif (3,) <= version_info < (3, 2): # pytest versions prior to 2.6.0 are not compatible with Python 3.1, # mostly due to accidental incompatibilities introduced because that is # not one of the officially supported platforms for pytest and so does # not get regular testing. Development version 2.6.0 has been tested # with this project and found to work correctly. #TODO: Once pytest 2.6.0 has been officially released change the pytest # requirement to ("==", "2.6.0"). pytest_version = (">=", lowest_version_string_with_prefix("2.6.0")), missing_argparse = True if current_environment: try: import argparse missing_argparse = False except ImportError: pass if missing_argparse: yield requirement_spec("argparse") if not pytest_version: pytest_version = (">=", _first_supported_pytest_version), yield requirement_spec("pytest", *pytest_version) def six_requirements(version_info=sys.version_info): """ Generate Python version specific six package requirements. The requirements are returned as setuptools/pip compatible requirement specification strings. """ if version_info < (2, 5): # 'six' release 1.5 dropped Python 2.4.x compatibility. yield requirement_spec("six", ("<", lowest_version_string_with_prefix("1.5"))) else: yield requirement_spec("six") def virtualenv_requirements(version_info=sys.version_info): """ Generate Python version specific virtualenv package requirements. The requirements are returned as setuptools/pip compatible requirement specification strings. """ if version_info < (2, 5): # 'virtualenv' release 1.8 dropped Python 2.4.x compatibility. yield requirement_spec("virtualenv", ("<", lowest_version_string_with_prefix("1.8"))) elif version_info < (2, 6): # 'virtualenv' release 1.10 dropped Python 2.5.x compatibility. yield requirement_spec("virtualenv", ("<", lowest_version_string_with_prefix("1.10"))) else: yield requirement_spec("virtualenv") suds-1.1.2/tools/suds_devel/utility.py000066400000000000000000000126601425611400200200430ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Generic functionality shared in different development utility modules. """ import os import os.path import sys if sys.version_info < (3, 0): from urllib import quote as url_quote else: from urllib.parse import quote as url_quote def any_contains_any(strings, candidates): """Whether any of the strings contains any of the candidates.""" for string in strings: for c in candidates: if c in string: return True class FileJanitor: """Janitor class for removing a specific file.""" def __init__(self, path): self.__path = path def clean(self): try: os.remove(self.__path) except (KeyboardInterrupt, SystemExit): raise except Exception: pass def lowest_version_string_with_prefix(prefix): """ The lowest possible version string with the given prefix. 'The lowest' according to the usual version string ordering used by setuptools, e.g. '2.4.3.dev0' is the lowest possible version in the 2.4.3 series. """ return "%s.dev0" % (prefix,) def path_to_URL(path, escape=True): """Convert a local file path to a absolute path file protocol URL.""" # We do not use urllib's builtin pathname2url() function since: # - it has been commented with 'not recommended for general use' # - it does not seem to work the same on Windows and non-Windows platforms # (result starts with /// on Windows but does not on others) # - urllib implementation prior to Python 2.5 used to quote ':' characters # as '|' which would confuse pip on Windows. url = os.path.abspath(path) for sep in (os.sep, os.altsep): if sep and sep != "/": url = url.replace(sep, "/") if escape: # Must not escape ':' or '/' or Python will not recognize those URLs # correctly. Detected on Windows 7 SP1 x64 with Python 3.4.0, but doing # this always does not hurt since both are valid ASCII characters. no_protocol_URL = url_quote(url, safe=":/") else: no_protocol_URL = url return "file:///%s" % (no_protocol_URL,) def report_error(message): print("ERROR: %s" % (message,)) def requirement_spec(package_name, *args): """Identifier used when specifying a requirement to pip or setuptools.""" if not args or args == (None,): return package_name version_specs = [] for version_spec in args: if isinstance(version_spec, (list, tuple)): operator, version = version_spec else: assert isinstance(version_spec, str) operator = "==" version = version_spec version_specs.append("%s%s" % (operator, version)) return "%s%s" % (package_name, ",".join(version_specs)) if sys.version_info < (2, 6): def _rel_path(path): # Older Python versions do not support os.path.relpath(), ergo no # pretty path formatting for them. return os.path.normpath(path) else: def _rel_path(path): try: return os.path.relpath(path) except ValueError: # Current and given path on different file systems, e.g. C: & D: # drives on Windows. return os.path.normpath(path) def script_folder(script_path): """ Return the given script's folder or None if it can not be determined. Script is identified by its __file__ attribute. If the given __file__ attribute value contains no path information, it is expected to identify an existing file in the current working folder. Returned folder may be specified relative to the current working folder and, if determined, will never be an empty string. Typical use case for calling this function is from a regular stand-alone script and not a frozen module or a module imported from the disk, a zip-file, an external database or any other such source. Such callers can safely assume they have a valid __file__ attribute available. """ # There exist modules whose __file__ attribute does not correspond directly # to a disk file, e.g. modules imported from inside zip archives. if os.path.isfile(script_path): return _rel_path(os.path.dirname(script_path)) or "." def path_iter(path): """Returns an iterator over all the file & folder names in a path.""" parts = [] while path: path, item = os.path.split(path) if item: parts.append(item) return reversed(parts) suds-1.1.2/tools/suds_devel/zip.py000066400000000000000000000075341425611400200171460ustar00rootroot00000000000000# -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify it under # the terms of the (LGPL) GNU Lesser General Public License as published by the # Free Software Foundation; either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License # for more details at ( http://www.gnu.org/licenses/lgpl.html ). # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # written by: Jurko Gospodnetić ( jurko.gospodnetic@pke.hr ) """ Zip compression related utilities. """ import os import os.path import sys import zipfile from suds_devel.utility import path_iter def zip_folder_content(folder, zip_file): success = False zippy = zipfile.ZipFile(zip_file, "w", _zip_compression()) try: archiver = _Archiver(zippy, folder) for root, folders, files in os.walk(folder): archiver.add_folder_with_files(root, files) success = True finally: zippy.close() if not success: os.remove(zip_file) class _Archiver: def __init__(self, zip_file, folder): self.__zip_file = zip_file self.__base_folder_parts = list(path_iter(folder)) def add_folder_with_files(self, folder, files): path_prefix = self.__path_prefix(folder) for file in files: assert file file_path = os.path.join(folder, file) self.__zip_file.write(file_path, path_prefix + file) # If no files are present in this folder and this is not the base # folder then we need to add the folder itself as an explicit entry. if not files and path_prefix: # Old Python versions did not support using the ZipFile.write() # method on folders so we do it manually by adding a 0-size entry # using ZipFile.writestr(). Encountered using Python 2.4.3. # N.B. An archived folder name must include a trailing slash, which # is exactly what we have in our prepared path_prefix value. self.__zip_file.writestr(path_prefix, "") def __path_prefix(self, folder): """ Path prefix to be used when archiving any items from the given folder. Expects the folder to be located under the base folder path and the returned path prefix does not include the base folder information. This makes sure we include just the base folder's content in the archive, and not the base folder itself. """ path_parts = path_iter(folder) _skip_expected(path_parts, self.__base_folder_parts) result = "/".join(path_parts) if result: result += "/" return result # Mimic the next() built-in introduced in Python 2.6. Does not need to be # perfect but only as good as we need it internally in this module. if sys.version_info < (2, 6): def _iter_next(iter): return iter.next() else: _iter_next = next def _skip_expected(iter, expected): for expected_value in expected: value = _iter_next(iter) assert value == expected_value _zip_compression_value = None def _zip_compression(): global _zip_compression_value if _zip_compression_value is None: try: import zlib _zip_compression_value = zipfile.ZIP_DEFLATED except ImportError: _zip_compression_value = zipfile.ZIP_STORED return _zip_compression_value