├── .gitignore ├── .readthedocs.yml ├── .travis.yml ├── LICENSE ├── PKG-INFO ├── README.md ├── docs ├── Makefile └── source │ ├── conf.py │ └── index.rst ├── examples ├── NET45_WCF │ ├── Main.cs │ ├── Main.exe │ └── run_service.bat ├── java-soap │ ├── run_service.sh │ └── src │ │ └── org │ │ ├── HelloService.java │ │ ├── HelloServicePublisher.java │ │ └── InfoService.java └── test_client.py ├── package.sh ├── publish.sh ├── requirements.txt ├── run_tests.sh ├── setup.cfg ├── setup.py ├── suds ├── __init__.py ├── bindings │ ├── __init__.py │ ├── binding.py │ ├── document.py │ ├── multiref.py │ └── rpc.py ├── builder.py ├── cache.py ├── client.py ├── compat.py ├── metrics.py ├── mx │ ├── __init__.py │ ├── appender.py │ ├── basic.py │ ├── core.py │ ├── encoded.py │ ├── literal.py │ └── typer.py ├── options.py ├── plugin.py ├── properties.py ├── reader.py ├── resolver.py ├── sax │ ├── __init__.py │ ├── attribute.py │ ├── date.py │ ├── document.py │ ├── element.py │ ├── enc.py │ ├── parser.py │ └── text.py ├── servicedefinition.py ├── serviceproxy.py ├── soaparray.py ├── store.py ├── sudsobject.py ├── transport │ ├── __init__.py │ ├── http.py │ ├── https.py │ └── options.py ├── umx │ ├── __init__.py │ ├── attrlist.py │ ├── basic.py │ ├── core.py │ ├── encoded.py │ └── typed.py ├── utils.py ├── wsdl.py ├── wsse.py └── xsd │ ├── __init__.py │ ├── deplist.py │ ├── doctor.py │ ├── query.py │ ├── schema.py │ ├── sxbase.py │ ├── sxbasic.py │ └── sxbuiltin.py └── tests ├── __init__.py ├── cache_test.py ├── encode_specials_test.py └── servicedefinition_test_skip.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .DS_Store 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Packages 9 | *.egg 10 | *.egg-info 11 | dist 12 | build 13 | eggs 14 | parts 15 | bin 16 | var 17 | sdist 18 | develop-eggs 19 | .installed.cfg 20 | lib 21 | lib64 22 | __pycache__ 23 | 24 | # Installer logs 25 | pip-log.txt 26 | 27 | # Unit test / coverage reports 28 | .coverage 29 | .tox 30 | nosetests.xml 31 | 32 | # Translations 33 | *.mo 34 | 35 | # Mr Developer 36 | .mr.developer.cfg 37 | .project 38 | .pydevproject 39 | 40 | tags 41 | suds_py3.egg-info/ 42 | dist-bak/ 43 | .vscode/ 44 | **/jaxws-ri/ 45 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | 3 | build: 4 | image: latest 5 | 6 | python: 7 | version: 3.5 8 | setup_py_install: false 9 | pip_install: false 10 | 11 | # Build PDF & ePub 12 | formats: 13 | - epub 14 | - pdf 15 | 16 | requirements_file: requirements.txt -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.5" 4 | install: 5 | - pip install -r requirements.txt 6 | script: 7 | - sh ./run_tests.sh -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. -------------------------------------------------------------------------------- /PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.1 2 | Name: suds-py3 3 | Version: 1.3.3.0 4 | Summary: Lightweight SOAP client 5 | Home-page: https://github.com/cackharot/suds-py3 6 | Author: Cackharot (original Jeff Ortel) 7 | Author-email: cackharot@gmail.com 8 | License: LGPL 9 | Description: The "suds-p3" is a lightweight soap-based client for python3 licensed under LGPL. This is a mirror of http://svn.fedorahosted.org/svn/suds/trunk/ supporting Python3 and some fixes. 10 | Keywords: soap,wsdl,basic http binding,basic auth 11 | Platform: Python3 12 | Classifier: Programming Language :: Python 13 | Classifier: Programming Language :: Python :: 3 14 | Classifier: Intended Audience :: Developers 15 | Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) 16 | Classifier: Operating System :: OS Independent 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | suds-py3 2 | ======== 3 | [![travisci](https://travis-ci.org/cackharot/suds-py3.svg?branch=master)](https://travis-ci.org/cackharot/suds-py3.svg?branch=master) 4 | [![readthedocs](https://readthedocs.org/projects/suds-py3/badge/?version=latest)](https://readthedocs.org/projects/suds-py3/badge/?version=latest) 5 | 6 | Suds is a lightweight SOAP python client for consuming Web Services. 7 | 8 | Mirror of http://svn.fedorahosted.org/svn/suds/trunk/ supporting Python3 and some fixes. 9 | 10 | ## Overview 11 | 12 | The "Suds" web services client is a lightweight soap-based client for python the is licensed under LGPL. 13 | 14 | For details, visit: 15 | * Project site: https://fedorahosted.org/suds/ 16 | * Documentation https://fedorahosted.org/suds/wiki/Documentation 17 | 18 | Since the original library is no longer supported and documentation also disappeared along with it. 19 | 20 | A copy of the documentation is hosted at https://suds-py3.readthedocs.io/en/latest/ 21 | 22 | This is not my original documentation however I have reformatted to sphinx rST style 23 | and updated few parts to keep the code examples clean and working. 24 | 25 | Pull requests are welcome for the `docs`. 26 | 27 | ## Features 28 | * No class generation 29 | * Provides an object API. 30 | * Reads wsdl at runtime for encoding/decoding 31 | * Supports the following SOAP binding styles: 32 | * Document/Literal 33 | * RPC/Literal 34 | * RPC/Encoded 35 | * Provides objectification of WSDL defined: 36 | * Types ''(objects)'' 37 | * Enumerations 38 | * Service and type objects provide inspection via ''print'' 39 | * Supports unicode 40 | * HTTP authentication 41 | * ''Basic'' WS-Security 42 | 43 | ## Installation 44 | ``` 45 | pip3 install suds-py3 46 | ``` 47 | 48 | ## Sample usage 49 | ``` 50 | from suds.client import Client 51 | client = Client('http://localhost:8181/soap/helloservice?wsdl', username='bob', password='catbob') 52 | result = client.service.sayHello('bob') 53 | # result -> "Hello, bob!" 54 | ``` 55 | 56 | ### Examples 57 | Examples folder contains sample SOAP services in JAVA, .NET WCF. 58 | 59 | Example has a python client that loads WSDL from `http://localhost:8181/soap/helloservice?wsdl` <- This is served by one of the below services. 60 | 61 | RUN Any one of the JAVA/.NET WCF services 62 | 63 | RUN `python examples/test_client.py` to test whether this package is working properly. 64 | 65 | **Running JAVA Soap service** 66 | * `cd examples/java-soap/ && sh run_service.sh` 67 | 68 | **Running .NET WCF service** 69 | * `cd examples/NET45_WCF/ && run_service.bat` <- Run this in Visual Studio developer command prompt 70 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = suds-py3 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | import os 16 | import sys 17 | sys.path.insert(0, os.path.abspath('../../')) 18 | 19 | # -- Project information ----------------------------------------------------- 20 | 21 | project = u'suds-py3' 22 | copyright = u'2018, cackharot' 23 | author = u'cackharot' 24 | 25 | # The short X.Y version 26 | version = u'1.3.1' 27 | # The full version, including alpha/beta/rc tags 28 | release = u'1.3.1' 29 | 30 | 31 | # -- General configuration --------------------------------------------------- 32 | 33 | # If your documentation needs a minimal Sphinx version, state it here. 34 | # 35 | # needs_sphinx = '1.0' 36 | 37 | # Add any Sphinx extension module names here, as strings. They can be 38 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 39 | # ones. 40 | extensions = [ 41 | 'sphinx.ext.autodoc', 42 | 'sphinx.ext.doctest', 43 | 'sphinx.ext.intersphinx', 44 | 'sphinx.ext.todo', 45 | 'sphinx.ext.coverage', 46 | 'sphinx.ext.imgmath', 47 | 'sphinx.ext.ifconfig', 48 | 'sphinx.ext.viewcode', 49 | 'sphinx.ext.githubpages', 50 | ] 51 | 52 | # Add any paths that contain templates here, relative to this directory. 53 | templates_path = ['_templates'] 54 | 55 | # The suffix(es) of source filenames. 56 | # You can specify multiple suffix as a list of string: 57 | # 58 | # source_suffix = ['.rst', '.md'] 59 | source_suffix = '.rst' 60 | 61 | # The master toctree document. 62 | master_doc = 'index' 63 | 64 | # The language for content autogenerated by Sphinx. Refer to documentation 65 | # for a list of supported languages. 66 | # 67 | # This is also used if you do content translation via gettext catalogs. 68 | # Usually you set "language" from the command line for these cases. 69 | language = None 70 | 71 | # List of patterns, relative to source directory, that match files and 72 | # directories to ignore when looking for source files. 73 | # This pattern also affects html_static_path and html_extra_path . 74 | exclude_patterns = [] 75 | 76 | # The name of the Pygments (syntax highlighting) style to use. 77 | pygments_style = 'sphinx' 78 | 79 | 80 | # -- Options for HTML output ------------------------------------------------- 81 | 82 | # The theme to use for HTML and HTML Help pages. See the documentation for 83 | # a list of builtin themes. 84 | # 85 | html_theme = 'alabaster' 86 | 87 | # Theme options are theme-specific and customize the look and feel of a theme 88 | # further. For a list of options available for each theme, see the 89 | # documentation. 90 | # 91 | # html_theme_options = {} 92 | 93 | # Add any paths that contain custom static files (such as style sheets) here, 94 | # relative to this directory. They are copied after the builtin static files, 95 | # so a file named "default.css" will overwrite the builtin "default.css". 96 | html_static_path = ['_static'] 97 | 98 | # Custom sidebar templates, must be a dictionary that maps document names 99 | # to template names. 100 | # 101 | # The default sidebars (for documents that don't match any pattern) are 102 | # defined by theme itself. Builtin themes are using these templates by 103 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 104 | # 'searchbox.html']``. 105 | # 106 | # html_sidebars = {} 107 | 108 | 109 | # -- Options for HTMLHelp output --------------------------------------------- 110 | 111 | # Output file base name for HTML help builder. 112 | htmlhelp_basename = 'suds-py3doc' 113 | 114 | 115 | # -- Options for LaTeX output ------------------------------------------------ 116 | 117 | latex_elements = { 118 | # The paper size ('letterpaper' or 'a4paper'). 119 | # 120 | # 'papersize': 'letterpaper', 121 | 122 | # The font size ('10pt', '11pt' or '12pt'). 123 | # 124 | # 'pointsize': '10pt', 125 | 126 | # Additional stuff for the LaTeX preamble. 127 | # 128 | # 'preamble': '', 129 | 130 | # Latex figure (float) alignment 131 | # 132 | # 'figure_align': 'htbp', 133 | } 134 | 135 | # Grouping the document tree into LaTeX files. List of tuples 136 | # (source start file, target name, title, 137 | # author, documentclass [howto, manual, or own class]). 138 | latex_documents = [ 139 | (master_doc, 'suds-py3.tex', u'suds-py3 Documentation', 140 | u'cackharot', 'manual'), 141 | ] 142 | 143 | 144 | # -- Options for manual page output ------------------------------------------ 145 | 146 | # One entry per manual page. List of tuples 147 | # (source start file, name, description, authors, manual section). 148 | man_pages = [ 149 | (master_doc, 'suds-py3', u'suds-py3 Documentation', 150 | [author], 1) 151 | ] 152 | 153 | 154 | # -- Options for Texinfo output ---------------------------------------------- 155 | 156 | # Grouping the document tree into Texinfo files. List of tuples 157 | # (source start file, target name, title, author, 158 | # dir menu entry, description, category) 159 | texinfo_documents = [ 160 | (master_doc, 'suds-py3', u'suds-py3 Documentation', 161 | author, 'suds-py3', 'One line description of project.', 162 | 'Miscellaneous'), 163 | ] 164 | 165 | 166 | # -- Options for Epub output ------------------------------------------------- 167 | 168 | # Bibliographic Dublin Core info. 169 | epub_title = project 170 | epub_author = author 171 | epub_publisher = author 172 | epub_copyright = copyright 173 | 174 | # The unique identifier of the text. This can be a ISBN number 175 | # or the project homepage. 176 | # 177 | # epub_identifier = '' 178 | 179 | # A unique identification for the text. 180 | # 181 | # epub_uid = '' 182 | 183 | # A list of files that should not be packed into the epub file. 184 | epub_exclude_files = ['search.html'] 185 | 186 | 187 | # -- Extension configuration ------------------------------------------------- 188 | 189 | # -- Options for intersphinx extension --------------------------------------- 190 | 191 | # Example configuration for intersphinx: refer to the Python standard library. 192 | intersphinx_mapping = {'https://docs.python.org/': None} 193 | 194 | # -- Options for todo extension ---------------------------------------------- 195 | 196 | # If true, `todo` and `todoList` produce output, else they produce nothing. 197 | todo_include_todos = True -------------------------------------------------------------------------------- /examples/NET45_WCF/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.ServiceModel; 6 | using System.ServiceModel.Description; 7 | using System.IdentityModel.Selectors; 8 | using System.Net; 9 | using System.ServiceModel.Security; 10 | 11 | namespace Cackharot.SampleSoapService 12 | { 13 | [ServiceContract] 14 | public interface IHelloService 15 | { 16 | [OperationContract] 17 | string sayHello(string name); 18 | 19 | [OperationContract] 20 | double add(double a, double b); 21 | } 22 | 23 | public class HelloService : IHelloService 24 | { 25 | public string sayHello(string name) 26 | { 27 | return string.Format("Hello {0}!", name); 28 | } 29 | 30 | public double add(double a, double b) 31 | { 32 | return a + b; 33 | } 34 | } 35 | 36 | public class CustomUserNamePasswordValidator : UserNamePasswordValidator 37 | { 38 | public override void Validate(string userName, string password) 39 | { 40 | if (!("bob".Equals(userName) && "catbob".Equals(password))) 41 | { 42 | throw new FaultException("Invalid username or password"); 43 | } 44 | } 45 | } 46 | 47 | public class Program 48 | { 49 | public static void Main(string[] args) 50 | { 51 | string url = "http://localhost:8181/soap/helloservice"; 52 | ServiceHost serviceHost = null; 53 | 54 | try 55 | { 56 | serviceHost = new ServiceHost(typeof(HelloService), new Uri(url)); 57 | 58 | var smb = new ServiceMetadataBehavior(); 59 | smb.HttpGetEnabled = true; 60 | serviceHost.Description.Behaviors.Add(smb); 61 | 62 | serviceHost.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom; 63 | serviceHost.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNamePasswordValidator(); 64 | 65 | serviceHost.AddServiceEndpoint(ServiceMetadataBehavior.MexContractName, MetadataExchangeBindings.CreateMexHttpBinding(), "mex"); 66 | var binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly); 67 | binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic; 68 | serviceHost.AddServiceEndpoint(typeof(IHelloService), binding, string.Empty); 69 | serviceHost.Open(); 70 | 71 | var endpoint = serviceHost.Description.Endpoints.First(); 72 | Console.WriteLine("The Hello service is running and is listening on:"); 73 | Console.WriteLine("{0} ({1})", endpoint.Address.ToString(), endpoint.Binding.Name); 74 | Console.WriteLine("\nPress any key to stop the service."); 75 | Console.ReadKey(); 76 | } 77 | catch (Exception e) 78 | { 79 | Console.WriteLine(e); 80 | } 81 | finally 82 | { 83 | if (serviceHost != null) 84 | { 85 | if (serviceHost.State == CommunicationState.Faulted) 86 | { 87 | serviceHost.Abort(); 88 | } 89 | else 90 | { 91 | serviceHost.Close(); 92 | } 93 | } 94 | } 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /examples/NET45_WCF/Main.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cackharot/suds-py3/1d92cc6297efee31bfd94b50b99c431505d7de21/examples/NET45_WCF/Main.exe -------------------------------------------------------------------------------- /examples/NET45_WCF/run_service.bat: -------------------------------------------------------------------------------- 1 | csc /reference:System.IdentityModel.dll Main.cs && Main.exe -------------------------------------------------------------------------------- /examples/java-soap/run_service.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | mkdir -p "build" && \ 3 | echo "Building services..." && \ 4 | javac -d "build" -cp "$(pwd)/jaxws-ri/lib/*" "$(pwd)/src/org/InfoService.java" && \ 5 | javac -d "build" -cp "$(pwd)/jaxws-ri/lib/*" "$(pwd)/src/org/HelloService.java" && \ 6 | javac -d "build" -cp "build:$(pwd)/jaxws-ri/lib/*" "$(pwd)/src/org/HelloServicePublisher.java" && \ 7 | java -cp "build:$(pwd)/jaxws-ri/lib/*" org.HelloServicePublisher 8 | -------------------------------------------------------------------------------- /examples/java-soap/src/org/HelloService.java: -------------------------------------------------------------------------------- 1 | package org; 2 | 3 | import jakarta.jws.WebService; 4 | import jakarta.jws.WebMethod; 5 | import java.util.*; 6 | 7 | @WebService 8 | public class HelloService { 9 | @WebMethod 10 | public String sayHello(String name) { 11 | return String.format("Hello, %s!", name); 12 | } 13 | 14 | @WebMethod 15 | public double add(double a, double b) { 16 | return a + b; 17 | } 18 | 19 | @WebMethod 20 | public Date addDate(Date inputDate, int days) { 21 | Calendar c = Calendar.getInstance(); 22 | c.setTime(inputDate); 23 | c.add(Calendar.DATE, days); 24 | Date dt = c.getTime(); 25 | return dt; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/java-soap/src/org/HelloServicePublisher.java: -------------------------------------------------------------------------------- 1 | package org; 2 | 3 | import org.HelloService; 4 | import org.InfoService; 5 | 6 | import com.sun.net.httpserver.BasicAuthenticator; 7 | import com.sun.net.httpserver.HttpContext; 8 | import com.sun.net.httpserver.HttpServer; 9 | 10 | import jakarta.xml.ws.Endpoint; 11 | import java.io.IOException; 12 | import java.net.InetSocketAddress; 13 | 14 | public class HelloServicePublisher { 15 | public static void main(String[] args) throws IOException { 16 | HttpServer server = HttpServer.create(new InetSocketAddress("localhost", 8181), 0); 17 | server.start(); 18 | 19 | Endpoint endpoint = Endpoint.create(new HelloService()); 20 | Endpoint endpoint_info = Endpoint.create(new InfoService()); 21 | 22 | publishService(server, endpoint, "/soap/helloservice"); 23 | publishService(server, endpoint_info, "/soap/infoservice"); 24 | } 25 | 26 | private static void publishService(HttpServer server, Endpoint endpoint, String path) { 27 | HttpContext context = server.createContext(path); 28 | 29 | context.setAuthenticator(new BasicAuthenticator("test") { 30 | @Override 31 | public boolean checkCredentials(String username, String pass) { 32 | return "bob".equals(username) && "catbob".equals(pass); 33 | } 34 | }); 35 | 36 | printInfo("http:/" + server.getAddress().toString() + context.getPath()); 37 | 38 | endpoint.publish(context); 39 | } 40 | 41 | private static void printInfo(String url) { 42 | System.out.println(String.format("Starting the service at %s", url)); 43 | System.out.println(String.format("See WSDL at %s?wsdl", url)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/java-soap/src/org/InfoService.java: -------------------------------------------------------------------------------- 1 | package org; 2 | 3 | import jakarta.jws.WebService; 4 | import jakarta.jws.WebMethod; 5 | import java.util.*; 6 | 7 | @WebService 8 | public class InfoService { 9 | @WebMethod 10 | public String getInfo(String name) { 11 | return String.format("Info, %s!", name); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/test_client.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | suds_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) 4 | sys.path.append(suds_path) 5 | 6 | from suds.client import Client 7 | 8 | def set_log(): 9 | import logging 10 | logging.basicConfig(level=logging.INFO) 11 | logging.getLogger('suds.client').setLevel(logging.DEBUG) 12 | logging.getLogger('suds.transport').setLevel(logging.DEBUG) 13 | # logging.getLogger('suds.xsd.schema').setLevel(logging.DEBUG) 14 | 15 | def call_service(url): 16 | client = Client(url, username='bob', password='catbob') 17 | do_call_service(client, url) 18 | 19 | def do_call_service(client, url): 20 | print("Calling: sayHello()") 21 | result = client.service.sayHello('Username') 22 | 23 | print("Result: %s" % result) 24 | a = 10.98 25 | b = 98.83 26 | print("Calling: add()") 27 | sum = client.service.add(a, b) 28 | print("Result: Sum of %f + %f = %f" % (a,b,sum)) 29 | 30 | print("Calling: addDate()") 31 | from datetime import datetime 32 | import time 33 | inputDate = datetime.now() 34 | dt = client.service.addDate(inputDate, 1) 35 | print("Result: %s" % dt) 36 | 37 | def test(url): 38 | client = Client(url) 39 | for p in client.sd[0].ports: 40 | for m, args in p[1]: 41 | if len(args) == 0: 42 | print(client.service[0][m]()) 43 | 44 | if __name__ == '__main__': 45 | # set_log() 46 | url = 'http://localhost:8181/soap/helloservice?wsdl' 47 | if len(sys.argv) > 1: 48 | url = sys.argv[1] 49 | 50 | call_service(url) 51 | # test('http://dati.meteotrentino.it/service.asmx?WSDL') 52 | client1 = Client("http://127.0.0.1:8181/soap/infoservice?wsdl", username='bob', password='catbob') 53 | print(client1.service.getInfo("Bob")) 54 | 55 | client2 = Client("http://127.0.0.1:8181/soap/infoservice?wsdl", username='bob', password='catbob') 56 | print(client2.service.getInfo("Test2")) 57 | 58 | client3 = Client("http://127.0.0.1:8181/soap/infoservice?wsdl", username='bob', password='catbob') 59 | print(client3.service.getInfo("Test3")) 60 | -------------------------------------------------------------------------------- /package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | python3 setup.py sdist 3 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #python3 setup.py sdist upload 3 | twine upload dist/* 4 | 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | appdirs==1.4.3 2 | packaging==16.8 3 | py==1.10.0 4 | pyparsing==2.2.0 5 | pytest==3.0.6 6 | six==1.10.0 7 | testutils==0.2.0 8 | -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | python -m unittest discover tests *_test.py 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [install] 2 | optimize = 1 3 | 4 | [egg_info] 5 | tag_build = 6 | tag_date = 0 7 | tag_svn_revision = 0 8 | 9 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # This program is free software; you can redistribute it and/or modify 4 | # it under the terms of the (LGPL) GNU Lesser General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Library Lesser General Public License for more details at 12 | # ( http://www.gnu.org/licenses/lgpl.html ). 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software 16 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 17 | 18 | import suds 19 | from setuptools import setup, find_packages 20 | 21 | setup( 22 | name='suds-py3', 23 | version=suds.__version__, 24 | description="Lightweight SOAP client", 25 | long_description="""The "suds-p3" is a lightweight soap-based client for python3 26 | licensed under LGPL. This is a mirror of http://svn.fedorahosted.org/svn/suds/trunk/ 27 | supporting Python3 and some fixes.""", 28 | author='Cackharot (original Jeff Ortel)', 29 | author_email='cackharot@gmail.com', 30 | packages=find_packages(exclude=['tests']), 31 | url='https://github.com/cackharot/suds-py3', 32 | test_suite='tests', 33 | license='LGPL', 34 | keywords='soap, wsdl,basic http binding, basic auth', 35 | platforms='Python3', 36 | classifiers=[ 37 | 'Programming Language :: Python', 38 | 'Programming Language :: Python :: 3', 39 | 'Intended Audience :: Developers', 40 | 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', 41 | 'Operating System :: OS Independent', 42 | ] 43 | ) 44 | -------------------------------------------------------------------------------- /suds/__init__.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Suds is a lightweight SOAP python client that provides a 19 | service proxy for Web Services. 20 | """ 21 | 22 | from .compat import basestring, unicode 23 | 24 | # 25 | # Project properties 26 | # 27 | 28 | __version__ = '1.4.4.1' 29 | __build__ = "IN 20210108" 30 | 31 | # 32 | # Exceptions 33 | # 34 | 35 | 36 | class MethodNotFound(Exception): 37 | def __init__(self, name): 38 | Exception.__init__(self, "Method not found: '%s'" % name) 39 | 40 | 41 | class PortNotFound(Exception): 42 | def __init__(self, name): 43 | Exception.__init__(self, "Port not found: '%s'" % name) 44 | 45 | 46 | class ServiceNotFound(Exception): 47 | def __init__(self, name): 48 | Exception.__init__(self, "Service not found: '%s'" % name) 49 | 50 | 51 | class TypeNotFound(Exception): 52 | def __init__(self, name): 53 | Exception.__init__(self, "Type not found: '%s'" % tostr(name)) 54 | 55 | 56 | class BuildError(Exception): 57 | msg = \ 58 | """ 59 | An error occured while building a instance of (%s). As a result 60 | the object you requested could not be constructed. It is recommended 61 | that you construct the type manually using a Suds object. 62 | Please open a ticket with a description of this error. 63 | Reason: %s 64 | """ 65 | 66 | def __init__(self, name, exception): 67 | Exception.__init__(self, BuildError.msg % (name, exception)) 68 | 69 | 70 | class SoapHeadersNotPermitted(Exception): 71 | msg = \ 72 | """ 73 | Method (%s) was invoked with SOAP headers. The WSDL does not 74 | define SOAP headers for this method. Retry without the soapheaders 75 | keyword argument. 76 | """ 77 | 78 | def __init__(self, name): 79 | Exception.__init__(self, self.msg % name) 80 | 81 | 82 | def smart_str(s, encoding='utf-8', errors='strict'): 83 | """ 84 | Returns a bytestring version of 's', encoded as specified in 'encoding'. 85 | 86 | If strings_only is True, don't convert (some) non-string-like objects. 87 | 88 | from django 89 | """ 90 | if not isinstance(s, basestring): 91 | try: 92 | return str(s) 93 | except UnicodeEncodeError: 94 | if isinstance(s, Exception): 95 | # An Exception subclass containing non-ASCII data that doesn't 96 | # know how to print itself properly. We shouldn't raise a 97 | # further exception. 98 | return ' '.join(smart_str(arg, encoding, errors) for arg in s) 99 | return unicode(s).encode(encoding, errors) 100 | elif isinstance(s, unicode): 101 | return s.encode(encoding, errors) 102 | elif s and encoding != 'utf-8': 103 | return s.decode('utf-8', errors).encode(encoding, errors) 104 | else: 105 | return s 106 | 107 | 108 | class WebFault(Exception): 109 | def __init__(self, fault, document): 110 | if hasattr(fault, 'faultstring'): 111 | Exception.__init__(self, smart_str("Server raised fault: '%s'" % fault.faultstring)) 112 | self.fault = fault 113 | self.document = document 114 | 115 | # 116 | # Logging 117 | # 118 | 119 | 120 | class Repr: 121 | def __init__(self, x): 122 | self.x = x 123 | 124 | def __str__(self): 125 | return repr(self.x) 126 | 127 | # 128 | # Utility 129 | # 130 | 131 | 132 | def tostr(object, encoding=None): 133 | """ get a unicode safe string representation of an object """ 134 | if isinstance(object, basestring): 135 | if encoding is None: 136 | return object 137 | else: 138 | return object.encode(encoding) 139 | if isinstance(object, tuple): 140 | s = ['('] 141 | for item in object: 142 | if isinstance(item, basestring): 143 | s.append(item) 144 | else: 145 | s.append(tostr(item)) 146 | s.append(', ') 147 | s.append(')') 148 | return ''.join(s) 149 | if isinstance(object, list): 150 | s = ['['] 151 | for item in object: 152 | if isinstance(item, basestring): 153 | s.append(item) 154 | else: 155 | s.append(tostr(item)) 156 | s.append(', ') 157 | s.append(']') 158 | return ''.join(s) 159 | if isinstance(object, dict): 160 | s = ['{'] 161 | for item in object.items(): 162 | if isinstance(item[0], basestring): 163 | s.append(item[0]) 164 | else: 165 | s.append(tostr(item[0])) 166 | s.append(' = ') 167 | if isinstance(item[1], basestring): 168 | s.append(item[1]) 169 | else: 170 | s.append(tostr(item[1])) 171 | s.append(', ') 172 | s.append('}') 173 | return ''.join(s) 174 | try: 175 | return unicode(object) 176 | except: 177 | return str(object) 178 | 179 | 180 | class null: 181 | """ 182 | The I{null} object. 183 | Used to pass NULL for optional XML nodes. 184 | """ 185 | pass 186 | 187 | class Object(object): 188 | """ 189 | The python 3 base Object 190 | """ 191 | pass 192 | 193 | def objid(obj): 194 | return obj.__class__.__name__ + ':' + hex(id(obj)) 195 | 196 | 197 | from .client import Client 198 | -------------------------------------------------------------------------------- /suds/bindings/__init__.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Provides modules containing classes to support Web Services (SOAP) 19 | bindings. 20 | """ 21 | -------------------------------------------------------------------------------- /suds/bindings/document.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Provides classes for the (WS) SOAP I{document/literal}. 19 | """ 20 | 21 | from logging import getLogger 22 | from suds.bindings.binding import Binding 23 | from suds.sax.element import Element 24 | 25 | log = getLogger(__name__) 26 | 27 | 28 | class Document(Binding): 29 | """ 30 | The document/literal style. Literal is the only (@use) supported 31 | since document/encoded is pretty much dead. 32 | Although the soap specification supports multiple documents within the soap 33 | , it is very uncommon. As such, suds presents an I{RPC} view of 34 | service methods defined with a single document parameter. This is done so 35 | that the user can pass individual parameters instead of one, single 36 | document. 37 | To support the complete specification, service methods defined withs 38 | multiple documents (multiple message parts), must present a I{document} 39 | view for that method. 40 | """ 41 | 42 | def bodycontent(self, method, args, kwargs): 43 | # 44 | # The I{wrapped} vs I{bare} style is detected in 2 ways. 45 | # If there is 2+ parts in the message then it is I{bare}. 46 | # If there is only (1) part and that part resolves to a builtin then 47 | # it is I{bare}. Otherwise, it is I{wrapped}. 48 | # 49 | if not len(method.soap.input.body.parts): 50 | return () 51 | wrapped = method.soap.input.body.wrapped 52 | if wrapped: 53 | pts = self.bodypart_types(method) 54 | root = self.document(pts[0]) 55 | else: 56 | root = [] 57 | n = 0 58 | for pd in self.param_defs(method): 59 | if n < len(args): 60 | value = args[n] 61 | else: 62 | value = kwargs.get(pd[0]) 63 | n += 1 64 | p = self.mkparam(method, pd, value) 65 | if p is None: 66 | continue 67 | if not wrapped: 68 | ns = pd[1].namespace('ns0') 69 | p.setPrefix(ns[0], ns[1]) 70 | root.append(p) 71 | return root 72 | 73 | def replycontent(self, method, body): 74 | wrapped = method.soap.output.body.wrapped 75 | if wrapped: 76 | return body[0].children 77 | else: 78 | return body.children 79 | 80 | def document(self, wrapper): 81 | """ 82 | Get the document root. For I{document/literal}, this is the 83 | name of the wrapper element qualifed by the schema tns. 84 | @param wrapper: The method name. 85 | @type wrapper: L{xsd.sxbase.SchemaObject} 86 | @return: A root element. 87 | @rtype: L{Element} 88 | """ 89 | tag = wrapper[1].name 90 | ns = wrapper[1].namespace('ns0') 91 | d = Element(tag, ns=ns) 92 | return d 93 | 94 | def mkparam(self, method, pdef, object): 95 | # 96 | # Expand list parameters into individual parameters 97 | # each with the type information. This is because in document 98 | # arrays are simply unbounded elements. 99 | # 100 | if isinstance(object, (list, tuple)): 101 | tags = [] 102 | for item in object: 103 | tags.append(self.mkparam(method, pdef, item)) 104 | return tags 105 | else: 106 | return Binding.mkparam(self, method, pdef, object) 107 | 108 | def param_defs(self, method): 109 | # 110 | # Get parameter definitions for document literal. 111 | # The I{wrapped} vs I{bare} style is detected in 2 ways. 112 | # If there is 2+ parts in the message then it is I{bare}. 113 | # If there is only (1) part and that part resolves to a builtin then 114 | # it is I{bare}. Otherwise, it is I{wrapped}. 115 | # 116 | pts = self.bodypart_types(method) 117 | wrapped = method.soap.input.body.wrapped 118 | if not wrapped: 119 | return pts 120 | result = [] 121 | # wrapped 122 | for p in pts: 123 | resolved = p[1].resolve() 124 | for child, ancestry in resolved: 125 | if child.isattr(): 126 | continue 127 | if self.bychoice(ancestry): 128 | log.debug('%s\ncontained by , excluded as param for %s()', 129 | child, 130 | method.name) 131 | continue 132 | result.append((child.name, child)) 133 | return result 134 | 135 | def returned_types(self, method): 136 | result = [] 137 | wrapped = method.soap.output.body.wrapped 138 | rts = self.bodypart_types(method, input=False) 139 | if wrapped: 140 | for pt in rts: 141 | resolved = pt.resolve(nobuiltin=True) 142 | for child, ancestry in resolved: 143 | result.append(child) 144 | break 145 | else: 146 | result += rts 147 | return result 148 | 149 | def bychoice(self, ancestry): 150 | """ 151 | The ancestry contains a 152 | @param ancestry: A list of ancestors. 153 | @type ancestry: list 154 | @return: True if contains 155 | @rtype: boolean 156 | """ 157 | for x in ancestry: 158 | if x.choice(): 159 | return True 160 | return False 161 | -------------------------------------------------------------------------------- /suds/bindings/multiref.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Provides classes for handling soap multirefs. 19 | """ 20 | 21 | from logging import getLogger 22 | 23 | log = getLogger(__name__) 24 | 25 | soapenc = (None, 'http://schemas.xmlsoap.org/soap/encoding/') 26 | 27 | 28 | class MultiRef: 29 | """ 30 | Resolves and replaces multirefs. 31 | @ivar nodes: A list of non-multiref nodes. 32 | @type nodes: list 33 | @ivar catalog: A dictionary of multiref nodes by id. 34 | @type catalog: dict 35 | """ 36 | 37 | def __init__(self): 38 | self.nodes = [] 39 | self.catalog = {} 40 | 41 | def process(self, body): 42 | """ 43 | Process the specified soap envelope body and replace I{multiref} node 44 | references with the contents of the referenced node. 45 | @param body: A soap envelope body node. 46 | @type body: L{Element} 47 | @return: The processed I{body} 48 | @rtype: L{Element} 49 | """ 50 | self.nodes = [] 51 | self.catalog = {} 52 | self.build_catalog(body) 53 | self.update(body) 54 | body.children = self.nodes 55 | return body 56 | 57 | def update(self, node): 58 | """ 59 | Update the specified I{node} by replacing the I{multiref} references 60 | with the contents of the referenced nodes and remove the I{href} 61 | attribute. 62 | @param node: A node to update. 63 | @type node: L{Element} 64 | @return: The updated node 65 | @rtype: L{Element} 66 | """ 67 | self.replace_references(node) 68 | for c in node.children: 69 | self.update(c) 70 | return node 71 | 72 | def replace_references(self, node): 73 | """ 74 | Replacing the I{multiref} references with the contents of the 75 | referenced nodes and remove the I{href} attribute. Warning: since 76 | the I{ref} is not cloned, 77 | @param node: A node to update. 78 | @type node: L{Element} 79 | """ 80 | href = node.getAttribute('href') 81 | if href is None: 82 | return 83 | id = href.getValue() 84 | ref = self.catalog.get(id) 85 | if ref is None: 86 | log.error('soap multiref: %s, not-resolved', id) 87 | return 88 | node.append(ref.children) 89 | node.setText(ref.getText()) 90 | for a in ref.attributes: 91 | if a.name != 'id': 92 | node.append(a) 93 | node.remove(href) 94 | 95 | def build_catalog(self, body): 96 | """ 97 | Create the I{catalog} of multiref nodes by id and the list of 98 | non-multiref nodes. 99 | @param body: A soap envelope body node. 100 | @type body: L{Element} 101 | """ 102 | for child in body.children: 103 | if self.soaproot(child): 104 | self.nodes.append(child) 105 | id = child.get('id') 106 | if id is None: 107 | self.build_catalog(child) 108 | else: 109 | key = '#%s' % id 110 | self.catalog[key] = child 111 | 112 | def soaproot(self, node): 113 | """ 114 | Get whether the specified I{node} is a soap encoded root. 115 | This is determined by examining @soapenc:root='1'. 116 | The node is considered to be a root when the attribute 117 | is not specified. 118 | @param node: A node to evaluate. 119 | @type node: L{Element} 120 | @return: True if a soap encoded root. 121 | @rtype: bool 122 | """ 123 | root = node.getAttribute('root', ns=soapenc) 124 | if root is None: 125 | return True 126 | else: 127 | return root.value == '1' 128 | -------------------------------------------------------------------------------- /suds/bindings/rpc.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Provides classes for the (WS) SOAP I{rpc/literal} and I{rpc/encoded} bindings. 19 | """ 20 | 21 | from logging import getLogger 22 | from suds.mx.encoded import Encoded as MxEncoded 23 | from suds.umx.encoded import Encoded as UmxEncoded 24 | from suds.bindings.binding import Binding, envns 25 | from suds.sax.element import Element 26 | 27 | log = getLogger(__name__) 28 | 29 | 30 | encns = ('SOAP-ENC', 'http://schemas.xmlsoap.org/soap/encoding/') 31 | 32 | 33 | class RPC(Binding): 34 | """ 35 | RPC/Literal binding style. 36 | """ 37 | 38 | def param_defs(self, method): 39 | return self.bodypart_types(method) 40 | 41 | def envelope(self, header, body): 42 | env = Binding.envelope(self, header, body) 43 | env.addPrefix(encns[0], encns[1]) 44 | env.set('%s:encodingStyle' % envns[0], 45 | 'http://schemas.xmlsoap.org/soap/encoding/') 46 | return env 47 | 48 | def bodycontent(self, method, args, kwargs): 49 | n = 0 50 | root = self.method(method) 51 | for pd in self.param_defs(method): 52 | if n < len(args): 53 | value = args[n] 54 | else: 55 | value = kwargs.get(pd[0]) 56 | p = self.mkparam(method, pd, value) 57 | if p is not None: 58 | root.append(p) 59 | n += 1 60 | return root 61 | 62 | def replycontent(self, method, body): 63 | return body[0].children 64 | 65 | def method(self, method): 66 | """ 67 | Get the document root. For I{rpc/(literal|encoded)}, this is the 68 | name of the method qualifed by the schema tns. 69 | @param method: A service method. 70 | @type method: I{service.Method} 71 | @return: A root element. 72 | @rtype: L{Element} 73 | """ 74 | ns = method.soap.input.body.namespace 75 | if ns[0] is None: 76 | ns = ('ns0', ns[1]) 77 | method = Element(method.name, ns=ns) 78 | return method 79 | 80 | 81 | class Encoded(RPC): 82 | """ 83 | RPC/Encoded (section 5) binding style. 84 | """ 85 | 86 | def marshaller(self): 87 | return MxEncoded(self.schema()) 88 | 89 | def unmarshaller(self, typed=True): 90 | """ 91 | Get the appropriate XML decoder. 92 | @return: Either the (basic|typed) unmarshaller. 93 | @rtype: L{UmxTyped} 94 | """ 95 | if typed: 96 | return UmxEncoded(self.schema()) 97 | else: 98 | return RPC.unmarshaller(self, typed) 99 | -------------------------------------------------------------------------------- /suds/builder.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | The I{builder} module provides an wsdl/xsd defined types factory 19 | """ 20 | 21 | from logging import getLogger 22 | from suds import TypeNotFound 23 | from .compat import basestring 24 | from suds.sudsobject import Factory 25 | 26 | log = getLogger(__name__) 27 | 28 | 29 | class Builder: 30 | """ Builder used to construct an object for types defined in the schema """ 31 | 32 | def __init__(self, resolver): 33 | """ 34 | @param resolver: A schema object name resolver. 35 | @type resolver: L{resolver.Resolver} 36 | """ 37 | self.resolver = resolver 38 | 39 | def build(self, name): 40 | "build an object for the specified typename as defined in the schema" 41 | if isinstance(name, basestring): 42 | type = self.resolver.find(name) 43 | if type is None: 44 | raise TypeNotFound(name) 45 | else: 46 | type = name 47 | cls = type.name 48 | if type.mixed(): 49 | data = Factory.property(cls) 50 | else: 51 | data = Factory.object(cls) 52 | resolved = type.resolve() 53 | md = data.__metadata__ 54 | md.sxtype = resolved 55 | md.ordering = self.ordering(resolved) 56 | history = [] 57 | self.add_attributes(data, resolved) 58 | for child, ancestry in type.children(): 59 | if self.skip_child(child, ancestry): 60 | continue 61 | self.process(data, child, history[:]) 62 | return data 63 | 64 | def process(self, data, type, history): 65 | """ process the specified type then process its children """ 66 | if type in history: 67 | return 68 | if type.enum(): 69 | return 70 | history.append(type) 71 | resolved = type.resolve() 72 | value = None 73 | if type.unbounded(): 74 | value = [] 75 | else: 76 | if len(resolved) > 0: 77 | if resolved.mixed(): 78 | value = Factory.property(resolved.name) 79 | md = value.__metadata__ 80 | md.sxtype = resolved 81 | else: 82 | value = Factory.object(resolved.name) 83 | md = value.__metadata__ 84 | md.sxtype = resolved 85 | md.ordering = self.ordering(resolved) 86 | setattr(data, type.name, value) 87 | if value is not None: 88 | data = value 89 | if not isinstance(data, list): 90 | self.add_attributes(data, resolved) 91 | for child, ancestry in resolved.children(): 92 | if self.skip_child(child, ancestry): 93 | continue 94 | self.process(data, child, history[:]) 95 | 96 | def add_attributes(self, data, type): 97 | """ add required attributes """ 98 | for attr, ancestry in type.attributes(): 99 | name = '_%s' % attr.name 100 | value = attr.get_default() 101 | setattr(data, name, value) 102 | 103 | def skip_child(self, child, ancestry): 104 | """ get whether or not to skip the specified child """ 105 | if child.any(): 106 | return True 107 | return any(x.choice() for x in ancestry) 108 | 109 | def ordering(self, type): 110 | """ get the ordering """ 111 | result = [] 112 | for child, ancestry in type.resolve(): 113 | name = child.name 114 | if child.name is None: 115 | continue 116 | if child.isattr(): 117 | name = '_%s' % child.name 118 | result.append(name) 119 | return result 120 | -------------------------------------------------------------------------------- /suds/compat.py: -------------------------------------------------------------------------------- 1 | 2 | basestring = unicode = str 3 | -------------------------------------------------------------------------------- /suds/metrics.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | The I{metrics} module defines classes and other resources 19 | designed for collecting and reporting performance metrics. 20 | """ 21 | 22 | import time 23 | from logging import getLogger 24 | from math import modf 25 | 26 | log = getLogger(__name__) 27 | 28 | 29 | class Timer: 30 | 31 | def __init__(self): 32 | self.started = 0 33 | self.stopped = 0 34 | 35 | def start(self): 36 | self.started = time.time() 37 | self.stopped = 0 38 | return self 39 | 40 | def stop(self): 41 | if self.started > 0: 42 | self.stopped = time.time() 43 | return self 44 | 45 | def duration(self): 46 | return self.stopped - self.started 47 | 48 | def __str__(self): 49 | if self.started == 0: 50 | return 'not-running' 51 | if self.started > 0 and self.stopped == 0: 52 | return 'started: %d (running)' % self.started 53 | duration = self.duration() 54 | jmod = lambda m: (m[1], m[0]*1000) 55 | if duration < 1: 56 | ms = (duration*1000) 57 | return '%d (ms)' % ms 58 | if duration < 60: 59 | m = modf(duration) 60 | return '%d.%.3d (seconds)' % jmod(m) 61 | m = modf(duration/60) 62 | return '%d.%.3d (minutes)' % jmod(m) 63 | -------------------------------------------------------------------------------- /suds/mx/__init__.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Provides modules containing classes to support 19 | marshalling (XML). 20 | """ 21 | 22 | from suds.sudsobject import Object 23 | 24 | 25 | class Content(Object): 26 | """ 27 | Marshaller Content. 28 | @ivar tag: The content tag. 29 | @type tag: str 30 | @ivar value: The content's value. 31 | @type value: I{any} 32 | """ 33 | 34 | extensions = [] 35 | 36 | def __init__(self, tag=None, value=None, **kwargs): 37 | """ 38 | @param tag: The content tag. 39 | @type tag: str 40 | @param value: The content's value. 41 | @type value: I{any} 42 | """ 43 | Object.__init__(self) 44 | self.tag = tag 45 | self.value = value 46 | for k, v in kwargs.items(): 47 | setattr(self, k, v) 48 | 49 | def __getattr__(self, name): 50 | if name not in self.__dict__: 51 | if name in self.extensions: 52 | v = None 53 | setattr(self, name, v) 54 | else: 55 | raise AttributeError('Content has no attribute %s' % name) 56 | else: 57 | v = self.__dict__[name] 58 | return v 59 | -------------------------------------------------------------------------------- /suds/mx/basic.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Provides basic I{marshaller} classes. 19 | """ 20 | 21 | from logging import getLogger 22 | from suds.mx import Content 23 | from suds.mx.core import Core 24 | 25 | log = getLogger(__name__) 26 | 27 | 28 | class Basic(Core): 29 | """ 30 | A I{basic} (untyped) marshaller. 31 | """ 32 | 33 | def process(self, value, tag=None): 34 | """ 35 | Process (marshal) the tag with the specified value using the 36 | optional type information. 37 | @param value: The value (content) of the XML node. 38 | @type value: (L{Object}|any) 39 | @param tag: The (optional) tag name for the value. The default is 40 | value.__class__.__name__ 41 | @type tag: str 42 | @return: An xml node. 43 | @rtype: L{Element} 44 | """ 45 | content = Content(tag=tag, value=value) 46 | result = Core.process(self, content) 47 | return result 48 | -------------------------------------------------------------------------------- /suds/mx/core.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Provides I{marshaller} core classes. 19 | """ 20 | 21 | from logging import getLogger 22 | from suds.mx.appender import ContentAppender 23 | from suds.sax.element import Element 24 | from suds.sax.document import Document 25 | from suds.sudsobject import Property 26 | 27 | 28 | log = getLogger(__name__) 29 | 30 | 31 | class Core: 32 | """ 33 | An I{abstract} marshaller. This class implement the core 34 | functionality of the marshaller. 35 | @ivar appender: A content appender. 36 | @type appender: L{ContentAppender} 37 | """ 38 | 39 | def __init__(self): 40 | """ 41 | """ 42 | self.appender = ContentAppender(self) 43 | 44 | def process(self, content): 45 | """ 46 | Process (marshal) the tag with the specified value using the 47 | optional type information. 48 | @param content: The content to process. 49 | @type content: L{Object} 50 | """ 51 | log.debug('processing:\n%s', content) 52 | self.reset() 53 | if content.tag is None: 54 | content.tag = content.value.__class__.__name__ 55 | document = Document() 56 | if isinstance(content.value, Property): 57 | root = self.node(content) # root is never used? 58 | self.append(document, content) 59 | else: 60 | self.append(document, content) 61 | return document.root() 62 | 63 | def append(self, parent, content): 64 | """ 65 | Append the specified L{content} to the I{parent}. 66 | @param parent: The parent node to append to. 67 | @type parent: L{Element} 68 | @param content: The content to append. 69 | @type content: L{Object} 70 | """ 71 | log.debug('appending parent:\n%s\ncontent:\n%s', parent, content) 72 | if self.start(content): 73 | self.appender.append(parent, content) 74 | self.end(parent, content) 75 | 76 | def reset(self): 77 | """ 78 | Reset the marshaller. 79 | """ 80 | pass 81 | 82 | def node(self, content): 83 | """ 84 | Create and return an XML node. 85 | @param content: The content for which proccessing has been suspended. 86 | @type content: L{Object} 87 | @return: An element. 88 | @rtype: L{Element} 89 | """ 90 | return Element(content.tag) 91 | 92 | def start(self, content): 93 | """ 94 | Appending this content has started. 95 | @param content: The content for which proccessing has started. 96 | @type content: L{Content} 97 | @return: True to continue appending 98 | @rtype: boolean 99 | """ 100 | return True 101 | 102 | def suspend(self, content): 103 | """ 104 | Appending this content has suspended. 105 | @param content: The content for which proccessing has been suspended. 106 | @type content: L{Content} 107 | """ 108 | pass 109 | 110 | def resume(self, content): 111 | """ 112 | Appending this content has resumed. 113 | @param content: The content for which proccessing has been resumed. 114 | @type content: L{Content} 115 | """ 116 | pass 117 | 118 | def end(self, parent, content): 119 | """ 120 | Appending this content has ended. 121 | @param parent: The parent node ending. 122 | @type parent: L{Element} 123 | @param content: The content for which proccessing has ended. 124 | @type content: L{Content} 125 | """ 126 | pass 127 | 128 | def setnil(self, node, content): 129 | """ 130 | Set the value of the I{node} to nill. 131 | @param node: A I{nil} node. 132 | @type node: L{Element} 133 | @param content: The content to set nil. 134 | @type content: L{Content} 135 | """ 136 | pass 137 | 138 | def setdefault(self, node, content): 139 | """ 140 | Set the value of the I{node} to a default value. 141 | @param node: A I{nil} node. 142 | @type node: L{Element} 143 | @param content: The content to set the default value. 144 | @type content: L{Content} 145 | @return: The default. 146 | """ 147 | pass 148 | 149 | def optional(self, content): 150 | """ 151 | Get whether the specified content is optional. 152 | @param content: The content which to check. 153 | @type content: L{Content} 154 | """ 155 | return False 156 | -------------------------------------------------------------------------------- /suds/mx/encoded.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Provides encoded I{marshaller} classes. 19 | """ 20 | 21 | from logging import getLogger 22 | from suds import * 23 | from suds.mx import * 24 | from suds.mx.literal import Literal 25 | from suds.mx.typer import Typer 26 | from suds.sudsobject import Factory, Object 27 | from suds.xsd.query import TypeQuery 28 | 29 | 30 | log = getLogger(__name__) 31 | 32 | # 33 | # Add encoded extensions 34 | # aty = The soap (section 5) encoded array type. 35 | # 36 | Content.extensions.append('aty') 37 | 38 | 39 | class Encoded(Literal): 40 | """ 41 | A SOAP section (5) encoding marshaller. 42 | This marshaller supports rpc/encoded soap styles. 43 | """ 44 | 45 | def start(self, content): 46 | # 47 | # For soap encoded arrays, the 'aty' (array type) information 48 | # is extracted and added to the 'content'. Then, the content.value 49 | # is replaced with an object containing an 'item=[]' attribute 50 | # containing values that are 'typed' suds objects. 51 | # 52 | start = Literal.start(self, content) 53 | if start and isinstance(content.value, (list, tuple)): 54 | resolved = content.type.resolve() 55 | for c in resolved: 56 | if hasattr(c[0], 'aty'): 57 | content.aty = (content.tag, c[0].aty) 58 | self.cast(content) 59 | break 60 | return start 61 | 62 | def end(self, parent, content): 63 | # 64 | # For soap encoded arrays, the soapenc:arrayType attribute is 65 | # added with proper type and size information. 66 | # Eg: soapenc:arrayType="xs:int[3]" 67 | # 68 | Literal.end(self, parent, content) 69 | if content.aty is None: 70 | return 71 | tag, aty = content.aty 72 | ns0 = ('at0', aty[1]) 73 | ns1 = ('at1', 'http://schemas.xmlsoap.org/soap/encoding/') 74 | array = content.value.item 75 | child = parent.getChild(tag) 76 | child.addPrefix(ns0[0], ns0[1]) 77 | child.addPrefix(ns1[0], ns1[1]) 78 | name = '%s:arrayType' % ns1[0] 79 | value = '%s:%s[%d]' % (ns0[0], aty[0], len(array)) 80 | child.set(name, value) 81 | 82 | def encode(self, node, content): 83 | if content.type.any(): 84 | Typer.auto(node, content.value) 85 | return 86 | if content.real.any(): 87 | Typer.auto(node, content.value) 88 | return 89 | ns = None 90 | name = content.real.name 91 | if self.xstq: 92 | ns = content.real.namespace() 93 | Typer.manual(node, name, ns) 94 | 95 | def cast(self, content): 96 | """ 97 | Cast the I{untyped} list items found in content I{value}. 98 | Each items contained in the list is checked for XSD type information. 99 | Items (values) that are I{untyped}, are replaced with suds objects and 100 | type I{metadata} is added. 101 | @param content: The content holding the collection. 102 | @type content: L{Content} 103 | @return: self 104 | @rtype: L{Encoded} 105 | """ 106 | aty = content.aty[1] 107 | resolved = content.type.resolve() 108 | array = Factory.object(resolved.name) 109 | array.item = [] 110 | query = TypeQuery(aty) 111 | ref = query.execute(self.schema) 112 | if ref is None: 113 | raise TypeNotFound(qref) 114 | for x in content.value: 115 | if isinstance(x, (list, tuple)): 116 | array.item.append(x) 117 | continue 118 | if isinstance(x, Object): 119 | md = x.__metadata__ 120 | md.sxtype = ref 121 | array.item.append(x) 122 | continue 123 | if isinstance(x, dict): 124 | x = Factory.object(ref.name, x) 125 | md = x.__metadata__ 126 | md.sxtype = ref 127 | array.item.append(x) 128 | continue 129 | x = Factory.property(ref.name, x) 130 | md = x.__metadata__ 131 | md.sxtype = ref 132 | array.item.append(x) 133 | content.value = array 134 | return self 135 | -------------------------------------------------------------------------------- /suds/mx/typer.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Provides sx typing classes. 19 | """ 20 | 21 | from logging import getLogger 22 | from suds import Object 23 | from suds.sax import Namespace as NS 24 | from suds.sax.text import Text 25 | 26 | log = getLogger(__name__) 27 | 28 | 29 | class Typer: 30 | """ 31 | Provides XML node typing as either automatic or manual. 32 | @cvar types: A dict of class to xs type mapping. 33 | @type types: dict 34 | """ 35 | 36 | types = { 37 | int: ('int', NS.xsdns), 38 | # long: ('long', NS.xsdns), 39 | float: ('float', NS.xsdns), 40 | str: ('string', NS.xsdns), 41 | # unicode: ('string', NS.xsdns), 42 | Text: ('string', NS.xsdns), 43 | bool: ('boolean', NS.xsdns), 44 | } 45 | 46 | @classmethod 47 | def auto(cls, node, value=None): 48 | """ 49 | Automatically set the node's xsi:type attribute based on either 50 | I{value}'s class or the class of the node's text. When I{value} is an 51 | unmapped class, the default type (xs:any) is set. 52 | @param node: An XML node 53 | @type node: L{sax.element.Element} 54 | @param value: An object that is or would be the node's text. 55 | @type value: I{any} 56 | @return: The specified node. 57 | @rtype: L{sax.element.Element} 58 | """ 59 | if value is None: 60 | value = node.getText() 61 | if isinstance(value, Object): 62 | known = cls.known(value) 63 | if known.name is None: 64 | return node 65 | tm = (known.name, known.namespace()) 66 | else: 67 | tm = cls.types.get(value.__class__, cls.types.get(str)) 68 | cls.manual(node, *tm) 69 | return node 70 | 71 | @classmethod 72 | def manual(cls, node, tval, ns=None): 73 | """ 74 | Set the node's xsi:type attribute based on either I{value}'s 75 | class or the class of the node's text. Then adds the referenced 76 | prefix(s) to the node's prefix mapping. 77 | @param node: An XML node 78 | @type node: L{sax.element.Element} 79 | @param tval: The name of the schema type. 80 | @type tval: str 81 | @param ns: The XML namespace of I{tval}. 82 | @type ns: (prefix, uri) 83 | @return: The specified node. 84 | @rtype: L{sax.element.Element} 85 | """ 86 | xta = ':'.join((NS.xsins[0], 'type')) 87 | node.addPrefix(NS.xsins[0], NS.xsins[1]) 88 | if ns is None: 89 | node.set(xta, tval) 90 | else: 91 | ns = cls.genprefix(node, ns) 92 | qname = ':'.join((ns[0], tval)) 93 | node.set(xta, qname) 94 | node.addPrefix(ns[0], ns[1]) 95 | return node 96 | 97 | @classmethod 98 | def genprefix(cls, node, ns): 99 | """ 100 | Generate a prefix. 101 | @param node: An XML node on which the prefix will be used. 102 | @type node: L{sax.element.Element} 103 | @param ns: A namespace needing an unique prefix. 104 | @type ns: (prefix, uri) 105 | @return: The I{ns} with a new prefix. 106 | """ 107 | for n in range(1, 1024): 108 | p = 'ns%d' % n 109 | u = node.resolvePrefix(p, default=None) 110 | if u is None or u == ns[1]: 111 | return (p, ns[1]) 112 | raise Exception('auto prefix, exhausted') 113 | 114 | @classmethod 115 | def known(cls, object): 116 | try: 117 | md = object.__metadata__ 118 | known = md.sxtype 119 | return known 120 | except: 121 | pass 122 | -------------------------------------------------------------------------------- /suds/options.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Suds basic options classes. 19 | """ 20 | 21 | from suds.properties import AutoLinker, Unskin, Skin, Definition 22 | from suds.wsse import Security 23 | from suds.xsd.doctor import Doctor 24 | from suds.transport import Transport 25 | from suds.cache import Cache, NoCache 26 | 27 | 28 | class TpLinker(AutoLinker): 29 | """ 30 | Transport (auto) linker used to manage linkage between 31 | transport objects Properties and those Properties that contain them. 32 | """ 33 | 34 | def updated(self, properties, prev, next): 35 | if isinstance(prev, Transport): 36 | tp = Unskin(prev.options) 37 | properties.unlink(tp) 38 | if isinstance(next, Transport): 39 | tp = Unskin(next.options) 40 | properties.link(tp) 41 | 42 | 43 | class Options(Skin): 44 | """ 45 | Options: 46 | - B{cache} - The XML document cache. May be set (None) for no caching. 47 | - type: L{Cache} 48 | - default: L{NoCache} 49 | - B{faults} - Raise faults raised by server, 50 | else return tuple from service method invocation as (httpcode, object). 51 | - type: I{bool} 52 | - default: True 53 | - B{service} - The default service name. 54 | - type: I{str} 55 | - default: None 56 | - B{port} - The default service port name, not tcp port. 57 | - type: I{str} 58 | - default: None 59 | - B{location} - This overrides the service port address I{URL} defined 60 | in the WSDL. 61 | - type: I{str} 62 | - default: None 63 | - B{transport} - The message transport. 64 | - type: L{Transport} 65 | - default: None 66 | - B{soapheaders} - The soap headers to be included in the soap message. 67 | - type: I{any} 68 | - default: None 69 | - B{wsse} - The web services I{security} provider object. 70 | - type: L{Security} 71 | - default: None 72 | - B{doctor} - A schema I{doctor} object. 73 | - type: L{Doctor} 74 | - default: None 75 | - B{xstq} - The B{x}ml B{s}chema B{t}ype B{q}ualified flag indicates 76 | that the I{xsi:type} attribute values should be qualified by namespace. 77 | - type: I{bool} 78 | - default: True 79 | - B{prefixes} - Elements of the soap message should be qualified (when needed) 80 | using XML prefixes as opposed to xmlns="" syntax. 81 | - type: I{bool} 82 | - default: True 83 | - B{retxml} - Flag that causes the I{raw} soap envelope to be returned instead 84 | of the python object graph. 85 | - type: I{bool} 86 | - default: False 87 | - B{prettyxml} - Flag that causes I{pretty} xml to be rendered when generating 88 | the outbound soap envelope. 89 | - type: I{bool} 90 | - default: False 91 | - B{autoblend} - Flag that ensures that the schema(s) defined within the 92 | WSDL import each other. 93 | - type: I{bool} 94 | - default: False 95 | - B{cachingpolicy} - The caching policy. 96 | - type: I{int} 97 | - 0 = Cache XML documents. 98 | - 1 = Cache WSDL (pickled) object. 99 | - default: 0 100 | - B{plugins} - A plugin container. 101 | - type: I{list} 102 | """ 103 | def __init__(self, **kwargs): 104 | domain = __name__ 105 | definitions = [ 106 | Definition('cache', Cache, NoCache()), 107 | Definition('faults', bool, True), 108 | Definition('transport', Transport, None, TpLinker()), 109 | Definition('service', (int, str), None), 110 | Definition('port', (int, str), None), 111 | Definition('location', str, None), 112 | Definition('soapheaders', (), ()), 113 | Definition('wsse', Security, None), 114 | Definition('doctor', Doctor, None), 115 | Definition('xstq', bool, True), 116 | Definition('prefixes', bool, True), 117 | Definition('retxml', bool, False), 118 | Definition('prettyxml', bool, False), 119 | Definition('autoblend', bool, False), 120 | Definition('cachingpolicy', int, 0), 121 | Definition('plugins', (list, tuple), []), 122 | ] 123 | Skin.__init__(self, domain, definitions, kwargs) 124 | -------------------------------------------------------------------------------- /suds/plugin.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | The plugin module provides classes for implementation 19 | of suds plugins. 20 | """ 21 | 22 | from logging import getLogger 23 | 24 | log = getLogger(__name__) 25 | 26 | 27 | class Context(object): 28 | """ 29 | Plugin context. 30 | """ 31 | pass 32 | 33 | 34 | class InitContext(Context): 35 | """ 36 | Init Context. 37 | @ivar wsdl: The wsdl. 38 | @type wsdl: L{wsdl.Definitions} 39 | """ 40 | pass 41 | 42 | 43 | class DocumentContext(Context): 44 | """ 45 | The XML document load context. 46 | @ivar url: The URL. 47 | @type url: str 48 | @ivar document: Either the XML text or the B{parsed} document root. 49 | @type document: (str|L{sax.element.Element}) 50 | """ 51 | pass 52 | 53 | 54 | class MessageContext(Context): 55 | """ 56 | The context for sending the soap envelope. 57 | @ivar envelope: The soap envelope to be sent. 58 | @type envelope: (str|L{sax.element.Element}) 59 | @ivar reply: The reply. 60 | @type reply: (str|L{sax.element.Element}|object) 61 | """ 62 | pass 63 | 64 | 65 | class Plugin: 66 | """ 67 | Plugin base. 68 | """ 69 | pass 70 | 71 | 72 | class InitPlugin(Plugin): 73 | """ 74 | The base class for suds I{init} plugins. 75 | """ 76 | 77 | def initialized(self, context): 78 | """ 79 | Suds client initialization. 80 | Called after wsdl the has been loaded. Provides the plugin 81 | with the opportunity to inspect/modify the WSDL. 82 | @param context: The init context. 83 | @type context: L{InitContext} 84 | """ 85 | pass 86 | 87 | 88 | class DocumentPlugin(Plugin): 89 | """ 90 | The base class for suds I{document} plugins. 91 | """ 92 | 93 | def loaded(self, context): 94 | """ 95 | Suds has loaded a WSDL/XSD document. Provides the plugin 96 | with an opportunity to inspect/modify the unparsed document. 97 | Called after each WSDL/XSD document is loaded. 98 | @param context: The document context. 99 | @type context: L{DocumentContext} 100 | """ 101 | pass 102 | 103 | def parsed(self, context): 104 | """ 105 | Suds has parsed a WSDL/XSD document. Provides the plugin 106 | with an opportunity to inspect/modify the parsed document. 107 | Called after each WSDL/XSD document is parsed. 108 | @param context: The document context. 109 | @type context: L{DocumentContext} 110 | """ 111 | pass 112 | 113 | 114 | class MessagePlugin(Plugin): 115 | """ 116 | The base class for suds I{soap message} plugins. 117 | """ 118 | 119 | def marshalled(self, context): 120 | """ 121 | Suds will send the specified soap envelope. 122 | Provides the plugin with the opportunity to inspect/modify 123 | the envelope Document before it is sent. 124 | @param context: The send context. 125 | The I{envelope} is the envelope docuemnt. 126 | @type context: L{MessageContext} 127 | """ 128 | pass 129 | 130 | def sending(self, context): 131 | """ 132 | Suds will send the specified soap envelope. 133 | Provides the plugin with the opportunity to inspect/modify 134 | the message text it is sent. 135 | @param context: The send context. 136 | The I{envelope} is the envelope text. 137 | @type context: L{MessageContext} 138 | """ 139 | pass 140 | 141 | def received(self, context): 142 | """ 143 | Suds has received the specified reply. 144 | Provides the plugin with the opportunity to inspect/modify 145 | the received XML text before it is SAX parsed. 146 | @param context: The reply context. 147 | The I{reply} is the raw text. 148 | @type context: L{MessageContext} 149 | """ 150 | pass 151 | 152 | def parsed(self, context): 153 | """ 154 | Suds has sax parsed the received reply. 155 | Provides the plugin with the opportunity to inspect/modify 156 | the sax parsed DOM tree for the reply before it is unmarshalled. 157 | @param context: The reply context. 158 | The I{reply} is DOM tree. 159 | @type context: L{MessageContext} 160 | """ 161 | pass 162 | 163 | def unmarshalled(self, context): 164 | """ 165 | Suds has unmarshalled the received reply. 166 | Provides the plugin with the opportunity to inspect/modify 167 | the unmarshalled reply object before it is returned. 168 | @param context: The reply context. 169 | The I{reply} is unmarshalled suds object. 170 | @type context: L{MessageContext} 171 | """ 172 | pass 173 | 174 | 175 | class PluginContainer: 176 | """ 177 | Plugin container provides easy method invocation. 178 | @ivar plugins: A list of plugin objects. 179 | @type plugins: [L{Plugin},] 180 | @cvar ctxclass: A dict of plugin method / context classes. 181 | @type ctxclass: dict 182 | """ 183 | 184 | domains = { 185 | 'init': (InitContext, InitPlugin), 186 | 'document': (DocumentContext, DocumentPlugin), 187 | 'message': (MessageContext, MessagePlugin), 188 | } 189 | 190 | def __init__(self, plugins): 191 | """ 192 | @param plugins: A list of plugin objects. 193 | @type plugins: [L{Plugin},] 194 | """ 195 | self.plugins = plugins 196 | 197 | def __getattr__(self, name): 198 | domain = self.domains.get(name) 199 | if domain: 200 | plugins = [] 201 | ctx, pclass = domain 202 | for p in self.plugins: 203 | if isinstance(p, pclass): 204 | plugins.append(p) 205 | return PluginDomain(ctx, plugins) 206 | else: 207 | raise Exception('plugin domain (%s), invalid' % name) 208 | 209 | 210 | class PluginDomain: 211 | """ 212 | The plugin domain. 213 | @ivar ctx: A context. 214 | @type ctx: L{Context} 215 | @ivar plugins: A list of plugins (targets). 216 | @type plugins: list 217 | """ 218 | 219 | def __init__(self, ctx, plugins): 220 | self.ctx = ctx 221 | self.plugins = plugins 222 | 223 | def __getattr__(self, name): 224 | return Method(name, self) 225 | 226 | 227 | class Method: 228 | """ 229 | Plugin method. 230 | @ivar name: The method name. 231 | @type name: str 232 | @ivar domain: The plugin domain. 233 | @type domain: L{PluginDomain} 234 | """ 235 | 236 | def __init__(self, name, domain): 237 | """ 238 | @param name: The method name. 239 | @type name: str 240 | @param domain: A plugin domain. 241 | @type domain: L{PluginDomain} 242 | """ 243 | self.name = name 244 | self.domain = domain 245 | 246 | def __call__(self, **kwargs): 247 | ctx = self.domain.ctx() 248 | ctx.__dict__.update(kwargs) 249 | for plugin in self.domain.plugins: 250 | try: 251 | method = getattr(plugin, self.name, None) 252 | if method and callable(method): 253 | method(ctx) 254 | except Exception as pe: 255 | log.exception(pe) 256 | return ctx 257 | -------------------------------------------------------------------------------- /suds/reader.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Contains xml document reader classes. 19 | """ 20 | import hashlib 21 | from logging import getLogger 22 | 23 | from suds.sax.parser import Parser 24 | from suds.transport import Request 25 | from suds.cache import NoCache 26 | from suds.store import DocumentStore 27 | from suds.plugin import PluginContainer 28 | 29 | log = getLogger(__name__) 30 | 31 | 32 | class Reader: 33 | """ 34 | The reader provides integration with cache. 35 | @ivar options: An options object. 36 | @type options: I{Options} 37 | """ 38 | 39 | def __init__(self, options): 40 | """ 41 | @param options: An options object. 42 | @type options: I{Options} 43 | """ 44 | self.options = options 45 | self.plugins = PluginContainer(options.plugins) 46 | 47 | def mangle(self, name, x): 48 | """ 49 | Mangle the name by hashing the I{name} and appending I{x}. 50 | @return: the mangled name. 51 | """ 52 | h = hashlib.sha256(name.encode('utf8')).hexdigest() 53 | return '%s-%s' % (h, x) 54 | 55 | 56 | class DocumentReader(Reader): 57 | """ 58 | The XML document reader provides an integration 59 | between the SAX L{Parser} and the document cache. 60 | """ 61 | 62 | def open(self, url): 63 | """ 64 | Open an XML document at the specified I{url}. 65 | First, the document attempted to be retrieved from 66 | the I{object cache}. If not found, it is downloaded and 67 | parsed using the SAX parser. The result is added to the 68 | cache for the next open(). 69 | @param url: A document url. 70 | @type url: str. 71 | @return: The specified XML document. 72 | @rtype: I{Document} 73 | """ 74 | cache = self.cache() 75 | id = self.mangle(url, 'document') 76 | d = cache.get(id) 77 | if d is None: 78 | d = self.download(url) 79 | cache.put(id, d) 80 | self.plugins.document.parsed(url=url, document=d.root()) 81 | return d 82 | 83 | def download(self, url): 84 | """ 85 | Download the docuemnt. 86 | @param url: A document url. 87 | @type url: str. 88 | @return: A file pointer to the docuemnt. 89 | @rtype: file-like 90 | """ 91 | store = DocumentStore() 92 | fp = store.open(url) 93 | if fp is None: 94 | fp = self.options.transport.open(Request(url)) 95 | content = fp.read() 96 | fp.close() 97 | ctx = self.plugins.document.loaded(url=url, document=content) 98 | content = ctx.document 99 | sax = Parser() 100 | return sax.parse(string=content) 101 | 102 | def cache(self): 103 | """ 104 | Get the cache. 105 | @return: The I{options} when I{cachingpolicy} = B{0}. 106 | @rtype: L{Cache} 107 | """ 108 | if self.options.cachingpolicy == 0: 109 | return self.options.cache 110 | else: 111 | return NoCache() 112 | 113 | 114 | class DefinitionsReader(Reader): 115 | """ 116 | The WSDL definitions reader provides an integration 117 | between the Definitions and the object cache. 118 | @ivar fn: A factory function (constructor) used to 119 | create the object not found in the cache. 120 | @type fn: I{Constructor} 121 | """ 122 | 123 | def __init__(self, options, fn): 124 | """ 125 | @param options: An options object. 126 | @type options: I{Options} 127 | @param fn: A factory function (constructor) used to 128 | create the object not found in the cache. 129 | @type fn: I{Constructor} 130 | """ 131 | Reader.__init__(self, options) 132 | self.fn = fn 133 | 134 | def open(self, url): 135 | """ 136 | Open a WSDL at the specified I{url}. 137 | First, the WSDL attempted to be retrieved from 138 | the I{object cache}. After unpickled from the cache, the 139 | I{options} attribute is restored. 140 | If not found, it is downloaded and instantiated using the 141 | I{fn} constructor and added to the cache for the next open(). 142 | @param url: A WSDL url. 143 | @type url: str. 144 | @return: The WSDL object. 145 | @rtype: I{Definitions} 146 | """ 147 | cache = self.cache() 148 | id = self.mangle(url, 'wsdl') 149 | d = cache.get(id) 150 | if d is None: 151 | d = self.fn(url, self.options) 152 | cache.put(id, d) 153 | else: 154 | d.options = self.options 155 | for imp in d.imports: 156 | imp.imported.options = self.options 157 | return d 158 | 159 | def cache(self): 160 | """ 161 | Get the cache. 162 | @return: The I{options} when I{cachingpolicy} = B{1}. 163 | @rtype: L{Cache} 164 | """ 165 | if self.options.cachingpolicy == 1: 166 | return self.options.cache 167 | else: 168 | return NoCache() 169 | -------------------------------------------------------------------------------- /suds/sax/__init__.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | The sax module contains a collection of classes that provide a 19 | (D)ocument (O)bject (M)odel representation of an XML document. 20 | The goal is to provide an easy, intuative interface for managing XML 21 | documents. Although, the term, DOM, is used above, this model is 22 | B{far} better. 23 | 24 | XML namespaces in suds are represented using a (2) element tuple 25 | containing the prefix and the URI. Eg: I{('tns', 'http://myns')} 26 | 27 | @var encoder: A I{pluggable} XML special character processor used to 28 | encode/decode strings. 29 | @type encoder: L{Encoder} 30 | """ 31 | 32 | from suds.sax.enc import Encoder 33 | 34 | # 35 | # pluggable XML special character encoder. 36 | # 37 | encoder = Encoder() 38 | 39 | 40 | def splitPrefix(name): 41 | """ 42 | Split the name into a tuple (I{prefix}, I{name}). The first element in 43 | the tuple is I{None} when the name does't have a prefix. 44 | @param name: A node name containing an optional prefix. 45 | @type name: basestring 46 | @return: A tuple containing the (2) parts of I{name} 47 | @rtype: (I{prefix}, I{name}) 48 | """ 49 | if isinstance(name, str) and ':' in name: 50 | return tuple(name.split(':', 1)) 51 | else: 52 | return (None, name) 53 | 54 | 55 | class Namespace: 56 | """ 57 | The namespace class represents XML namespaces. 58 | """ 59 | 60 | default = (None, None) 61 | xmlns = ('xml', 'http://www.w3.org/XML/1998/namespace') 62 | xsdns = ('xs', 'http://www.w3.org/2001/XMLSchema') 63 | xsins = ('xsi', 'http://www.w3.org/2001/XMLSchema-instance') 64 | all = (xsdns, xsins) 65 | 66 | @classmethod 67 | def create(cls, p=None, u=None): 68 | return (p, u) 69 | 70 | @classmethod 71 | def none(cls, ns): 72 | return ns == cls.default 73 | 74 | @classmethod 75 | def xsd(cls, ns): 76 | try: 77 | return cls.w3(ns) and ns[1].endswith('XMLSchema') 78 | except: 79 | pass 80 | return False 81 | 82 | @classmethod 83 | def xsi(cls, ns): 84 | try: 85 | return cls.w3(ns) and ns[1].endswith('XMLSchema-instance') 86 | except: 87 | pass 88 | return False 89 | 90 | @classmethod 91 | def xs(cls, ns): 92 | return cls.xsd(ns) or cls.xsi(ns) 93 | 94 | @classmethod 95 | def w3(cls, ns): 96 | try: 97 | return ns[1].startswith('http://www.w3.org') 98 | except: 99 | pass 100 | return False 101 | 102 | @classmethod 103 | def isns(cls, ns): 104 | try: 105 | return isinstance(ns, tuple) and len(ns) == len(cls.default) 106 | except: 107 | pass 108 | return False 109 | -------------------------------------------------------------------------------- /suds/sax/attribute.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Provides XML I{attribute} classes. 19 | """ 20 | 21 | from logging import getLogger 22 | from suds.sax import Namespace, splitPrefix 23 | from suds.sax.text import Text 24 | from suds.compat import unicode 25 | 26 | log = getLogger(__name__) 27 | 28 | 29 | class Attribute: 30 | """ 31 | An XML attribute object. 32 | @ivar parent: The node containing this attribute 33 | @type parent: L{element.Element} 34 | @ivar prefix: The I{optional} namespace prefix. 35 | @type prefix: basestring 36 | @ivar name: The I{unqualified} name of the attribute 37 | @type name: basestring 38 | @ivar value: The attribute's value 39 | @type value: basestring 40 | """ 41 | def __init__(self, name, value=None): 42 | """ 43 | @param name: The attribute's name with I{optional} namespace prefix. 44 | @type name: basestring 45 | @param value: The attribute's value 46 | @type value: basestring 47 | """ 48 | self.parent = None 49 | self.prefix, self.name = splitPrefix(name) 50 | self.setValue(value) 51 | 52 | def clone(self, parent=None): 53 | """ 54 | Clone this object. 55 | @param parent: The parent for the clone. 56 | @type parent: L{element.Element} 57 | @return: A copy of this object assigned to the new parent. 58 | @rtype: L{Attribute} 59 | """ 60 | a = Attribute(self.qname(), self.value) 61 | a.parent = parent 62 | return a 63 | 64 | def qname(self): 65 | """ 66 | Get the B{fully} qualified name of this attribute 67 | @return: The fully qualified name. 68 | @rtype: basestring 69 | """ 70 | if self.prefix is None: 71 | return self.name 72 | else: 73 | return ':'.join((self.prefix, self.name)) 74 | 75 | def setValue(self, value): 76 | """ 77 | Set the attributes value 78 | @param value: The new value (may be None) 79 | @type value: basestring 80 | @return: self 81 | @rtype: L{Attribute} 82 | """ 83 | if isinstance(value, Text): 84 | self.value = value 85 | else: 86 | self.value = Text(value) 87 | return self 88 | 89 | def getValue(self, default=Text('')): 90 | """ 91 | Get the attributes value with optional default. 92 | @param default: An optional value to be return when the 93 | attribute's has not been set. 94 | @type default: basestring 95 | @return: The attribute's value, or I{default} 96 | @rtype: L{Text} 97 | """ 98 | if self.hasText(): 99 | return self.value 100 | else: 101 | return default 102 | 103 | def hasText(self): 104 | """ 105 | Get whether the attribute has I{text} and that it is not an empty 106 | (zero length) string. 107 | @return: True when has I{text}. 108 | @rtype: boolean 109 | """ 110 | return self.value is not None and len(self.value) 111 | 112 | def namespace(self): 113 | """ 114 | Get the attributes namespace. This may either be the namespace 115 | defined by an optional prefix, or its parent's namespace. 116 | @return: The attribute's namespace 117 | @rtype: (I{prefix}, I{name}) 118 | """ 119 | if self.prefix is None: 120 | return Namespace.default 121 | else: 122 | return self.resolvePrefix(self.prefix) 123 | 124 | def resolvePrefix(self, prefix): 125 | """ 126 | Resolve the specified prefix to a known namespace. 127 | @param prefix: A declared prefix 128 | @type prefix: basestring 129 | @return: The namespace that has been mapped to I{prefix} 130 | @rtype: (I{prefix}, I{name}) 131 | """ 132 | ns = Namespace.default 133 | if self.parent is not None: 134 | ns = self.parent.resolvePrefix(prefix) 135 | return ns 136 | 137 | def match(self, name=None, ns=None): 138 | """ 139 | Match by (optional) name and/or (optional) namespace. 140 | @param name: The optional attribute tag name. 141 | @type name: str 142 | @param ns: An optional namespace. 143 | @type ns: (I{prefix}, I{name}) 144 | @return: True if matched. 145 | @rtype: boolean 146 | """ 147 | if name is None: 148 | byname = True 149 | else: 150 | byname = self.name == name 151 | if ns is None: 152 | byns = True 153 | else: 154 | byns = self.namespace()[1] == ns[1] 155 | return byname and byns 156 | 157 | def __eq__(self, rhs): 158 | """ equals operator """ 159 | return rhs is not None and \ 160 | isinstance(rhs, Attribute) and \ 161 | self.prefix == rhs.name and \ 162 | self.name == rhs.name 163 | 164 | def __repr__(self): 165 | """ get a string representation """ 166 | return 'attr (prefix=%s, name=%s, value=(%s))' % ( 167 | self.prefix, 168 | self.name, 169 | self.value) 170 | 171 | def __str__(self): 172 | """ get an xml string representation """ 173 | n = self.qname() 174 | if self.hasText(): 175 | v = self.value.escape() 176 | else: 177 | v = self.value 178 | return u'%s="%s"' % (n, v) 179 | 180 | def __unicode__(self): 181 | """ get an xml string representation """ 182 | n = self.qname() 183 | if self.hasText(): 184 | v = self.value.escape() 185 | else: 186 | v = self.value 187 | return u'%s="%s"' % (n, v) 188 | -------------------------------------------------------------------------------- /suds/sax/document.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Provides XML I{document} classes. 19 | """ 20 | 21 | from logging import getLogger 22 | from suds.sax.element import Element 23 | 24 | log = getLogger(__name__) 25 | 26 | 27 | class Document(Element): 28 | """ simple document """ 29 | 30 | DECL = '' 31 | 32 | def __init__(self, root=None): 33 | Element.__init__(self, 'document') 34 | if root is not None: 35 | self.append(root) 36 | 37 | def root(self): 38 | if len(self.children): 39 | return self.children[0] 40 | else: 41 | return None 42 | 43 | def str(self): 44 | s = [] 45 | s.append(self.DECL) 46 | s.append('\n') 47 | if self.root() is not None: 48 | s.append(self.root().str()) 49 | return ''.join(s) 50 | 51 | def plain(self): 52 | s = [] 53 | s.append(self.DECL) 54 | s.append(self.root().plain()) 55 | return ''.join(s) 56 | 57 | def __str__(self): 58 | return self.str() 59 | 60 | def __unicode__(self): 61 | return self.str() 62 | -------------------------------------------------------------------------------- /suds/sax/enc.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Provides XML I{special character} encoder classes. 19 | """ 20 | 21 | import re 22 | 23 | 24 | class Encoder: 25 | """ 26 | An XML special character encoder/decoder. 27 | @cvar encodings: A mapping of special characters encoding. 28 | @type encodings: [(str,str)] 29 | @cvar decodings: A mapping of special characters decoding. 30 | @type decodings: [(str,str)] 31 | @cvar special: A list of special characters 32 | @type special: [char] 33 | """ 34 | 35 | encodings = ( 36 | ('&', '&'), 37 | ('<', '<'), 38 | ('>', '>'), 39 | ('"', '"'), 40 | ("'", ''') 41 | ) 42 | decodings = ( 43 | ('<', '<'), 44 | ('>', '>'), 45 | ('"', '"'), 46 | (''', "'"), 47 | ('&', '&') 48 | ) 49 | special = ('&', '<', '>', '"', "'") 50 | 51 | def needsEncoding(self, s): 52 | """ 53 | Get whether string I{s} contains special characters. 54 | @param s: A string to check. 55 | @type s: str 56 | @return: True if needs encoding. 57 | @rtype: boolean 58 | """ 59 | if isinstance(s, str): 60 | for c in self.special: 61 | if c in s: 62 | return True 63 | return False 64 | 65 | def encode(self, s): 66 | """ 67 | Encode special characters found in string I{s}. 68 | @param s: A string to encode. 69 | @type s: str 70 | @return: The encoded string. 71 | @rtype: str 72 | """ 73 | if isinstance(s, str) and self.needsEncoding(s): 74 | for x in self.encodings: 75 | s = re.sub(x[0], x[1], s) 76 | return s 77 | 78 | def decode(self, s): 79 | """ 80 | Decode special characters encodings found in string I{s}. 81 | @param s: A string to decode. 82 | @type s: str 83 | @return: The decoded string. 84 | @rtype: str 85 | """ 86 | if isinstance(s, str) and '&' in s: 87 | for x in self.decodings: 88 | s = s.replace(x[0], x[1]) 89 | return s 90 | -------------------------------------------------------------------------------- /suds/sax/parser.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | The sax module contains a collection of classes that provide a 19 | (D)ocument (O)bject (M)odel representation of an XML document. 20 | The goal is to provide an easy, intuative interface for managing XML 21 | documents. Although, the term, DOM, is used above, this model is 22 | B{far} better. 23 | 24 | XML namespaces in suds are represented using a (2) element tuple 25 | containing the prefix and the URI. Eg: I{('tns', 'http://myns')} 26 | 27 | """ 28 | 29 | from logging import getLogger 30 | from suds.compat import unicode 31 | from suds import metrics 32 | from suds.sax.document import Document 33 | from suds.sax.element import Element 34 | from suds.sax.text import Text 35 | from suds.sax.attribute import Attribute 36 | from xml.sax import make_parser, ContentHandler, parseString 37 | from xml.sax.handler import feature_external_ges 38 | 39 | log = getLogger(__name__) 40 | 41 | 42 | class Handler(ContentHandler): 43 | """ sax hanlder """ 44 | 45 | def __init__(self): 46 | self.nodes = [Document()] 47 | 48 | def startElement(self, name, attrs): 49 | top = self.top() 50 | node = Element(unicode(name), parent=top) 51 | for a in attrs.getNames(): 52 | n = unicode(a) 53 | v = unicode(attrs.getValue(a)) 54 | attribute = Attribute(n, v) 55 | if self.mapPrefix(node, attribute): 56 | continue 57 | node.append(attribute) 58 | node.charbuffer = [] 59 | top.append(node) 60 | self.push(node) 61 | 62 | def mapPrefix(self, node, attribute): 63 | skip = False 64 | if attribute.name == 'xmlns': 65 | if len(attribute.value): 66 | node.expns = unicode(attribute.value) 67 | skip = True 68 | elif attribute.prefix == 'xmlns': 69 | prefix = attribute.name 70 | node.nsprefixes[prefix] = unicode(attribute.value) 71 | skip = True 72 | return skip 73 | 74 | def endElement(self, name): 75 | name = unicode(name) 76 | current = self.top() 77 | if len(current.charbuffer): 78 | current.text = Text(u''.join(current.charbuffer)) 79 | del current.charbuffer 80 | if len(current): 81 | current.trim() 82 | currentqname = current.qname() 83 | if name == currentqname: 84 | self.pop() 85 | else: 86 | raise Exception('malformed document') 87 | 88 | def characters(self, content): 89 | text = unicode(content) 90 | node = self.top() 91 | node.charbuffer.append(text) 92 | 93 | def push(self, node): 94 | self.nodes.append(node) 95 | return node 96 | 97 | def pop(self): 98 | return self.nodes.pop() 99 | 100 | def top(self): 101 | return self.nodes[len(self.nodes)-1] 102 | 103 | 104 | class Parser: 105 | """ SAX Parser """ 106 | 107 | @classmethod 108 | def saxparser(cls): 109 | p = make_parser() 110 | p.setFeature(feature_external_ges, 0) 111 | h = Handler() 112 | p.setContentHandler(h) 113 | return (p, h) 114 | 115 | def parse(self, file=None, string=None): 116 | """ 117 | SAX parse XML text. 118 | @param file: Parse a python I{file-like} object. 119 | @type file: I{file-like} object. 120 | @param string: Parse string XML. 121 | @type string: str 122 | """ 123 | timer = metrics.Timer() 124 | timer.start() 125 | sax, handler = self.saxparser() 126 | if file is not None: 127 | sax.parse(file) 128 | timer.stop() 129 | metrics.log.debug('sax (%s) duration: %s', file, timer) 130 | return handler.nodes[0] 131 | if string is not None: 132 | if isinstance(string, str): 133 | string = string.encode() 134 | parseString(string, handler) 135 | timer.stop() 136 | metrics.log.debug('%s\nsax duration: %s', string, timer) 137 | return handler.nodes[0] 138 | -------------------------------------------------------------------------------- /suds/sax/text.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Contains XML text classes. 19 | """ 20 | 21 | from suds import sax 22 | 23 | 24 | class Text(str): 25 | """ 26 | An XML text object used to represent text content. 27 | @ivar lang: The (optional) language flag. 28 | @type lang: bool 29 | @ivar escaped: The (optional) XML special character escaped flag. 30 | @type escaped: bool 31 | """ 32 | __slots__ = ('lang', 'escaped',) 33 | 34 | @classmethod 35 | def __valid(cls, *args): 36 | return len(args) and args[0] is not None 37 | 38 | def __new__(cls, *args, **kwargs): 39 | if cls.__valid(*args): 40 | lang = kwargs.pop('lang', None) 41 | escaped = kwargs.pop('escaped', False) 42 | result = super(Text, cls).__new__(cls, *args, **kwargs) 43 | result.lang = lang 44 | result.escaped = escaped 45 | else: 46 | result = None 47 | return result 48 | 49 | def escape(self): 50 | """ 51 | Encode (escape) special XML characters. 52 | @return: The text with XML special characters escaped. 53 | @rtype: L{Text} 54 | """ 55 | if not self.escaped: 56 | post = sax.encoder.encode(self) 57 | escaped = post != self 58 | return Text(post, lang=self.lang, escaped=escaped) 59 | return self 60 | 61 | def unescape(self): 62 | """ 63 | Decode (unescape) special XML characters. 64 | @return: The text with escaped XML special characters decoded. 65 | @rtype: L{Text} 66 | """ 67 | if self.escaped: 68 | post = sax.encoder.decode(self) 69 | return Text(post, lang=self.lang) 70 | return self 71 | 72 | def trim(self): 73 | post = self.strip() 74 | return Text(post, lang=self.lang, escaped=self.escaped) 75 | 76 | def __add__(self, other): 77 | joined = u''.join((self, other)) 78 | result = Text(joined, lang=self.lang, escaped=self.escaped) 79 | if isinstance(other, Text): 80 | result.escaped = self.escaped or other.escaped 81 | return result 82 | 83 | def __repr__(self): 84 | s = [self] 85 | if self.lang is not None: 86 | s.append(' [%s]' % self.lang) 87 | if self.escaped: 88 | s.append(' ') 89 | return ''.join(s).__repr__() 90 | 91 | def __getstate__(self): 92 | state = {} 93 | for k in self.__slots__: 94 | state[k] = getattr(self, k) 95 | return state 96 | 97 | def __setstate__(self, state): 98 | for k in self.__slots__: 99 | setattr(self, k, state[k]) 100 | 101 | 102 | class Raw(Text): 103 | """ 104 | Raw text which is not XML escaped. 105 | This may include I{string} XML. 106 | """ 107 | def escape(self): 108 | return self 109 | 110 | def unescape(self): 111 | return self 112 | 113 | def __add__(self, other): 114 | joined = u''.join((self, other)) 115 | return Raw(joined, lang=self.lang) 116 | -------------------------------------------------------------------------------- /suds/servicedefinition.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | The I{service definition} provides a textual representation of a service. 19 | """ 20 | 21 | from logging import getLogger 22 | from suds import tostr 23 | import suds.metrics as metrics 24 | from suds.sax import Namespace 25 | 26 | log = getLogger(__name__) 27 | 28 | 29 | class ServiceDefinition: 30 | """ 31 | A service definition provides an object used to generate a textual 32 | description of a service. 33 | @ivar wsdl: A wsdl. 34 | @type wsdl: L{wsdl.Definitions} 35 | @ivar service: The service object. 36 | @type service: L{suds.wsdl.Service} 37 | @ivar ports: A list of port-tuple: (port, [(method-name, pdef)]) 38 | @type ports: [port-tuple,..] 39 | @ivar prefixes: A list of remapped prefixes. 40 | @type prefixes: [(prefix,uri),..] 41 | @ivar types: A list of type definitions 42 | @type types: [I{Type},..] 43 | """ 44 | 45 | def __init__(self, wsdl, service): 46 | """ 47 | @param wsdl: A wsdl object 48 | @type wsdl: L{Definitions} 49 | @param service: A service B{name}. 50 | @type service: str 51 | """ 52 | self.wsdl = wsdl 53 | self.service = service 54 | self.ports = [] 55 | self.params = [] 56 | self.types = [] 57 | self.prefixes = [] 58 | self.addports() 59 | self.paramtypes() 60 | self.publictypes() 61 | self.getprefixes() 62 | self.pushprefixes() 63 | 64 | def pushprefixes(self): 65 | """ 66 | Add our prefixes to the wsdl so that when users invoke methods 67 | and reference the prefixes, the will resolve properly. 68 | """ 69 | for ns in self.prefixes: 70 | self.wsdl.root.addPrefix(ns[0], ns[1]) 71 | 72 | def addports(self): 73 | """ 74 | Look through the list of service ports and construct a list of tuples 75 | where each tuple is used to describe a port and it's list of methods 76 | as: (port, [method]). Each method is tuple: (name, [pdef,..] where 77 | each pdef is a tuple: (param-name, type). 78 | """ 79 | timer = metrics.Timer() 80 | timer.start() 81 | for port in self.service.ports: 82 | p = self.findport(port) 83 | for op in port.binding.operations.values(): 84 | m = p[0].method(op.name) 85 | binding = m.binding.input 86 | method = (m.name, binding.param_defs(m)) 87 | p[1].append(method) 88 | metrics.log.debug("method '%s' created: %s", m.name, timer) 89 | p[1].sort() 90 | timer.stop() 91 | 92 | def findport(self, port): 93 | """ 94 | Find and return a port tuple for the specified port. 95 | Created and added when not found. 96 | @param port: A port. 97 | @type port: I{service.Port} 98 | @return: A port tuple. 99 | @rtype: (port, [method]) 100 | """ 101 | for p in self.ports: 102 | if p[0] == p: 103 | return p 104 | p = (port, []) 105 | self.ports.append(p) 106 | return p 107 | 108 | def getprefixes(self): 109 | """ 110 | Add prefixes foreach namespace referenced by parameter types. 111 | """ 112 | namespaces = [] 113 | for l in (self.params, self.types): 114 | for t, r in l: 115 | ns = r.namespace() 116 | if ns[1] is None: 117 | continue 118 | if ns[1] in namespaces: 119 | continue 120 | if Namespace.xs(ns) or Namespace.xsd(ns): 121 | continue 122 | namespaces.append(ns[1]) 123 | if t == r: 124 | continue 125 | ns = t.namespace() 126 | if ns[1] is None: 127 | continue 128 | if ns[1] in namespaces: 129 | continue 130 | namespaces.append(ns[1]) 131 | namespaces.sort() 132 | for u in namespaces: 133 | p = self.nextprefix() 134 | ns = (p, u) 135 | self.prefixes.append(ns) 136 | 137 | def paramtypes(self): 138 | """ get all parameter types """ 139 | for m in [p[1] for p in self.ports]: 140 | for p in [p[1] for p in m]: 141 | for pd in p: 142 | if pd[1] in self.params: 143 | continue 144 | item = (pd[1], pd[1].resolve()) 145 | self.params.append(item) 146 | 147 | def publictypes(self): 148 | """ get all public types """ 149 | for t in self.wsdl.schema.types.values(): 150 | if t in self.params: 151 | continue 152 | if t in self.types: 153 | continue 154 | item = (t, t) 155 | self.types.append(item) 156 | self.types.sort(key=lambda x: x[0].name) 157 | 158 | def nextprefix(self): 159 | """ 160 | Get the next available prefix. This means a prefix starting with 'ns' 161 | with a number appended as (ns0, ns1, ..) that is not already defined 162 | on the wsdl document. 163 | """ 164 | used = [ns[0] for ns in self.prefixes] 165 | used += [ns[0] for ns in self.wsdl.root.nsprefixes.items()] 166 | for n in range(0, 1024): 167 | p = 'ns%d' % n 168 | if p not in used: 169 | return p 170 | raise Exception('prefixes exhausted') 171 | 172 | def getprefix(self, u): 173 | """ 174 | Get the prefix for the specified namespace (uri) 175 | @param u: A namespace uri. 176 | @type u: str 177 | @return: The namspace. 178 | @rtype: (prefix, uri). 179 | """ 180 | for ns in Namespace.all: 181 | if u == ns[1]: 182 | return ns[0] 183 | for ns in self.prefixes: 184 | if u == ns[1]: 185 | return ns[0] 186 | raise Exception('ns (%s) not mapped' % u) 187 | 188 | def xlate(self, type): 189 | """ 190 | Get a (namespace) translated I{qualified} name for specified type. 191 | @param type: A schema type. 192 | @type type: I{suds.xsd.sxbasic.SchemaObject} 193 | @return: A translated I{qualified} name. 194 | @rtype: str 195 | """ 196 | resolved = type.resolve() 197 | name = resolved.name 198 | if type.unbounded(): 199 | name += '[]' 200 | ns = resolved.namespace() 201 | if ns[1] == self.wsdl.tns[1]: 202 | return name 203 | prefix = self.getprefix(ns[1]) 204 | return ':'.join((prefix, name)) 205 | 206 | def description(self, html=False): 207 | """ 208 | Get a textual description of the service for which this object 209 | represents. 210 | @return: A textual description. 211 | @rtype: str 212 | """ 213 | s = [] 214 | if html: 215 | indent = lambda n: '

%*s' % (n * 3, ' ') 216 | line = '


' 217 | else: 218 | indent = lambda n: '\n%*s' % (n * 3, ' ') 219 | line = '\n' + '-'*80 220 | s.append('Service ( %s ) tns="%s"' % 221 | (self.service.name, self.wsdl.tns[1])) 222 | s.append(indent(1)) 223 | s.append('Prefixes (%d)' % len(self.prefixes)) 224 | for p in self.prefixes: 225 | s.append(indent(2)) 226 | s.append('%s = "%s"' % p) 227 | s.append(indent(1)) 228 | s.append('Ports (%d):' % len(self.ports)) 229 | for p in self.ports: 230 | s.append(indent(2)) 231 | s.append('(%s)' % p[0].name) 232 | s.append(indent(3)) 233 | s.append('Methods (%d):' % len(p[1])) 234 | for m in p[1]: 235 | sig = [] 236 | s.append(indent(4)) 237 | sig.append(m[0]) 238 | sig.append('(') 239 | for p in m[1]: 240 | sig.append(self.xlate(p[1])) 241 | sig.append(' ') 242 | sig.append(p[0]) 243 | sig.append(', ') 244 | sig.append(')') 245 | try: 246 | s.append(''.join(sig)) 247 | except: 248 | pass 249 | s.append(indent(3)) 250 | s.append('Types (%d):' % len(self.types)) 251 | for t in self.types: 252 | s.append(indent(4)) 253 | s.append(self.xlate(t[0])) 254 | s.append(line) 255 | return ''.join(s) 256 | 257 | def __str__(self): 258 | try: 259 | return self.description() 260 | except Exception as e: 261 | log.exception(e) 262 | return tostr(e) 263 | 264 | def __repr__(self): 265 | return self.__str__() 266 | 267 | def html(self): 268 | try: 269 | return self.description(html=True) 270 | except Exception as e: 271 | log.exception(e) 272 | return tostr(e) 273 | -------------------------------------------------------------------------------- /suds/serviceproxy.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | The service proxy provides access to web services. 19 | 20 | Replaced by: L{client.Client} 21 | """ 22 | 23 | from logging import getLogger 24 | 25 | from .client import Client 26 | from .compat import unicode 27 | from .utils import is_builtin 28 | log = getLogger(__name__) 29 | 30 | 31 | class ServiceProxy(object): 32 | 33 | """ 34 | A lightweight soap based web service proxy. 35 | @ivar __client__: A client. 36 | Everything is delegated to the 2nd generation API. 37 | @type __client__: L{Client} 38 | @note: Deprecated, replaced by L{Client}. 39 | """ 40 | 41 | def __init__(self, url, **kwargs): 42 | """ 43 | @param url: The URL for the WSDL. 44 | @type url: str 45 | @param kwargs: keyword arguments. 46 | @keyword faults: Raise faults raised by server (default:True), 47 | else return tuple from service method invocation as 48 | (http code, object). 49 | @type faults: boolean 50 | @keyword proxy: An http proxy to be specified on requests (default:{}). 51 | The proxy is defined as {protocol:proxy,} 52 | @type proxy: dict 53 | """ 54 | client = Client(url, **kwargs) 55 | self.__client__ = client 56 | 57 | def get_instance(self, name): 58 | """ 59 | Get an instance of a WSDL type by name 60 | @param name: The name of a type defined in the WSDL. 61 | @type name: str 62 | @return: An instance on success, else None 63 | @rtype: L{sudsobject.Object} 64 | """ 65 | return self.__client__.factory.create(name) 66 | 67 | def get_enum(self, name): 68 | """ 69 | Get an instance of an enumeration defined in the WSDL by name. 70 | @param name: The name of a enumeration defined in the WSDL. 71 | @type name: str 72 | @return: An instance on success, else None 73 | @rtype: L{sudsobject.Object} 74 | """ 75 | return self.__client__.factory.create(name) 76 | 77 | def __str__(self): 78 | return str(self.__client__) 79 | 80 | def __unicode__(self): 81 | return str(self.__client__) 82 | 83 | def __getattr__(self, name): 84 | if is_builtin(name): 85 | return self.__dict__[name] 86 | else: 87 | return getattr(self.__client__.service, name) 88 | -------------------------------------------------------------------------------- /suds/soaparray.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | The I{soaparray} module provides XSD extensions for handling 19 | soap (section 5) encoded arrays. 20 | """ 21 | 22 | from suds.xsd.sxbasic import Factory as SXFactory 23 | from suds.xsd.sxbasic import Attribute as SXAttribute 24 | 25 | 26 | class Attribute(SXAttribute): 27 | """ 28 | Represents an XSD that handles special 29 | attributes that are extensions for WSDLs. 30 | @ivar aty: Array type information. 31 | @type aty: The value of wsdl:arrayType. 32 | """ 33 | 34 | def __init__(self, schema, root, aty): 35 | """ 36 | @param aty: Array type information. 37 | @type aty: The value of wsdl:arrayType. 38 | """ 39 | SXAttribute.__init__(self, schema, root) 40 | if aty.endswith('[]'): 41 | self.aty = aty[:-2] 42 | else: 43 | self.aty = aty 44 | 45 | def autoqualified(self): 46 | aqs = SXAttribute.autoqualified(self) 47 | aqs.append('aty') 48 | return aqs 49 | 50 | def description(self): 51 | d = SXAttribute.description(self) 52 | d = d+('aty',) 53 | return d 54 | 55 | # 56 | # Builder function, only builds Attribute when arrayType 57 | # attribute is defined on root. 58 | # 59 | 60 | 61 | def __fn(x, y): 62 | ns = (None, "http://schemas.xmlsoap.org/wsdl/") 63 | aty = y.get('arrayType', ns=ns) 64 | if aty is None: 65 | return SXAttribute(x, y) 66 | else: 67 | return Attribute(x, y, aty) 68 | 69 | # 70 | # Remap tags to __fn() builder. 71 | # 72 | SXFactory.maptag('attribute', __fn) 73 | -------------------------------------------------------------------------------- /suds/transport/__init__.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Contains transport interface (classes). 19 | """ 20 | 21 | 22 | class TransportError(Exception): 23 | def __init__(self, reason, httpcode, fp=None): 24 | Exception.__init__(self, reason) 25 | self.httpcode = httpcode 26 | self.fp = fp 27 | 28 | 29 | class Request: 30 | """ 31 | A transport request 32 | @ivar url: The url for the request. 33 | @type url: str 34 | @ivar message: The message to be sent in a POST request. 35 | @type message: str 36 | @ivar headers: The http headers to be used for the request. 37 | @type headers: dict 38 | """ 39 | 40 | def __init__(self, url, message=None): 41 | """ 42 | @param url: The url for the request. 43 | @type url: str 44 | @param message: The (optional) message to be send in the request. 45 | @type message: str 46 | """ 47 | self.url = url 48 | self.headers = {} 49 | self.message = message 50 | 51 | def __str__(self): 52 | s = [] 53 | s.append('URL:%s' % self.url) 54 | s.append('HEADERS: %s' % self.headers) 55 | s.append('MESSAGE:') 56 | s.append(str(self.message)) 57 | return '\n'.join(s) 58 | 59 | 60 | class Reply: 61 | """ 62 | A transport reply 63 | @ivar code: The http code returned. 64 | @type code: int 65 | @ivar message: The message to be sent in a POST request. 66 | @type message: str 67 | @ivar headers: The http headers to be used for the request. 68 | @type headers: dict 69 | """ 70 | 71 | def __init__(self, code, headers, message): 72 | """ 73 | @param code: The http code returned. 74 | @type code: int 75 | @param headers: The http returned headers. 76 | @type headers: dict 77 | @param message: The (optional) reply message received. 78 | @type message: str 79 | """ 80 | self.code = code 81 | self.headers = headers 82 | self.message = message 83 | 84 | def __str__(self): 85 | s = [] 86 | s.append('CODE: %s' % self.code) 87 | s.append('HEADERS: %s' % self.headers) 88 | s.append('MESSAGE:') 89 | s.append(str(self.message)) 90 | return '\n'.join(s) 91 | 92 | 93 | class Transport: 94 | """ 95 | The transport I{interface}. 96 | """ 97 | 98 | def __init__(self): 99 | """ 100 | Constructor. 101 | """ 102 | from suds.transport.options import Options 103 | self.options = Options() 104 | del Options 105 | 106 | def open(self, request): 107 | """ 108 | Open the url in the specified request. 109 | @param request: A transport request. 110 | @type request: L{Request} 111 | @return: An input stream. 112 | @rtype: stream 113 | @raise TransportError: On all transport errors. 114 | """ 115 | raise Exception('not-implemented') 116 | 117 | def send(self, request): 118 | """ 119 | Send soap message. Implementations are expected to handle: 120 | - proxies 121 | - I{http} headers 122 | - cookies 123 | - sending message 124 | - brokering exceptions into L{TransportError} 125 | @param request: A transport request. 126 | @type request: L{Request} 127 | @return: The reply 128 | @rtype: L{Reply} 129 | @raise TransportError: On all transport errors. 130 | """ 131 | raise Exception('not-implemented') 132 | -------------------------------------------------------------------------------- /suds/transport/http.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Contains classes for basic HTTP transport implementations. 19 | """ 20 | 21 | import urllib.request as u2 22 | from urllib.error import HTTPError 23 | from base64 import b64encode 24 | import socket 25 | from suds.transport import Transport, TransportError, Reply 26 | from suds.properties import Unskin 27 | from http.cookiejar import CookieJar 28 | from logging import getLogger 29 | 30 | log = getLogger(__name__) 31 | 32 | 33 | class HttpTransport(Transport): 34 | """ 35 | HTTP transport using urllib2. Provided basic http transport 36 | that provides for cookies, proxies but no authentication. 37 | """ 38 | 39 | def __init__(self, **kwargs): 40 | """ 41 | @param kwargs: Keyword arguments. 42 | - B{proxy} - An http proxy to be specified on requests. 43 | The proxy is defined as {protocol:proxy,} 44 | - type: I{dict} 45 | - default: {} 46 | - B{timeout} - Set the url open timeout (seconds). 47 | - type: I{float} 48 | - default: 90 49 | """ 50 | Transport.__init__(self) 51 | Unskin(self.options).update(kwargs) 52 | self.cookiejar = CookieJar() 53 | self.proxy = {} 54 | self.urlopener = None 55 | 56 | def open(self, request): 57 | try: 58 | url = request.url 59 | headers = request.headers 60 | log.debug('opening (%s)', url) 61 | u2request = u2.Request(url, None, headers) 62 | self.proxy = self.options.proxy 63 | return self.u2open(u2request) 64 | except HTTPError as e: 65 | raise TransportError(str(e), e.code, e.fp) 66 | 67 | def send(self, request): 68 | result = None 69 | url = request.url 70 | msg = request.message 71 | headers = request.headers 72 | try: 73 | u2request = u2.Request(url, msg, headers) 74 | self.addcookies(u2request) 75 | self.proxy = self.options.proxy 76 | request.headers.update(u2request.headers) 77 | log.debug('sending:\n%s', request) 78 | fp = self.u2open(u2request) 79 | self.getcookies(fp, u2request) 80 | result = Reply(200, fp.headers, fp.read()) 81 | log.debug('received:\n%s', result) 82 | except HTTPError as e: 83 | if e.code in (202, 204): 84 | result = None 85 | else: 86 | raise TransportError(e.msg, e.code, e.fp) 87 | return result 88 | 89 | def addcookies(self, u2request): 90 | """ 91 | Add cookies in the cookiejar to the request. 92 | @param u2request: A urllib2 request. 93 | @rtype: u2request: urllib2.Requet. 94 | """ 95 | self.cookiejar.add_cookie_header(u2request) 96 | 97 | def getcookies(self, fp, u2request): 98 | """ 99 | Add cookies in the request to the cookiejar. 100 | @param u2request: A urllib2 request. 101 | @rtype: u2request: urllib2.Requet. 102 | """ 103 | self.cookiejar.extract_cookies(fp, u2request) 104 | 105 | def u2open(self, u2request): 106 | """ 107 | Open a connection. 108 | @param u2request: A urllib2 request. 109 | @type u2request: urllib2.Requet. 110 | @return: The opened file-like urllib2 object. 111 | @rtype: fp 112 | """ 113 | tm = self.options.timeout 114 | url = self.u2opener() 115 | if self.u2ver() < 2.6: 116 | socket.setdefaulttimeout(tm) 117 | return url.open(u2request) 118 | else: 119 | return url.open(u2request, timeout=tm) 120 | 121 | def u2opener(self): 122 | """ 123 | Create a urllib opener. 124 | @return: An opener. 125 | @rtype: I{OpenerDirector} 126 | """ 127 | if self.urlopener is None: 128 | return u2.build_opener(*self.u2handlers()) 129 | else: 130 | return self.urlopener 131 | 132 | def u2handlers(self): 133 | """ 134 | Get a collection of urllib handlers. 135 | @return: A list of handlers to be installed in the opener. 136 | @rtype: [Handler,...] 137 | """ 138 | handlers = [] 139 | handlers.append(u2.ProxyHandler(self.proxy)) 140 | return handlers 141 | 142 | def u2ver(self): 143 | """ 144 | Get the major/minor version of the urllib2 lib. 145 | @return: The urllib2 version. 146 | @rtype: float 147 | """ 148 | try: 149 | part = u2.__version__.split('.', 1) 150 | n = float('.'.join(part)) 151 | return n 152 | except Exception as e: 153 | log.exception(e) 154 | return 0 155 | 156 | def __deepcopy__(self, memo={}): 157 | clone = self.__class__() 158 | p = Unskin(self.options) 159 | cp = Unskin(clone.options) 160 | cp.update(p) 161 | return clone 162 | 163 | 164 | class HttpAuthenticated(HttpTransport): 165 | """ 166 | Provides basic http authentication for servers that don't follow 167 | the specified challenge / response model. This implementation 168 | appends the I{Authorization} http header with base64 encoded 169 | credentials on every http request. 170 | """ 171 | 172 | def open(self, request): 173 | self.addcredentials(request) 174 | return HttpTransport.open(self, request) 175 | 176 | def send(self, request): 177 | self.addcredentials(request) 178 | return HttpTransport.send(self, request) 179 | 180 | def addcredentials(self, request): 181 | credentials = self.credentials() 182 | if not (None in credentials): 183 | encoded = b64encode(':'.join(credentials).encode('utf-8')).decode("ascii") 184 | basic = 'Basic %s' % encoded 185 | request.headers['Authorization'] = basic 186 | 187 | def credentials(self): 188 | return (self.options.username, self.options.password) 189 | -------------------------------------------------------------------------------- /suds/transport/https.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Contains classes for basic HTTP (authenticated) transport implementations. 19 | """ 20 | 21 | from suds.transport.http import HttpTransport 22 | from logging import getLogger 23 | from urllib.request import ( 24 | HTTPPasswordMgrWithDefaultRealm, 25 | HTTPBasicAuthHandler) 26 | 27 | log = getLogger(__name__) 28 | 29 | 30 | class HttpAuthenticated(HttpTransport): 31 | """ 32 | Provides basic http authentication that follows the RFC-2617 specification. 33 | As defined by specifications, credentials are provided to the server 34 | upon request (HTTP/1.0 401 Authorization Required) by the server only. 35 | @ivar pm: The password manager. 36 | @ivar handler: The authentication handler. 37 | """ 38 | 39 | def __init__(self, **kwargs): 40 | """ 41 | @param kwargs: Keyword arguments. 42 | - B{proxy} - An http proxy to be specified on requests. 43 | The proxy is defined as {protocol:proxy,} 44 | - type: I{dict} 45 | - default: {} 46 | - B{timeout} - Set the url open timeout (seconds). 47 | - type: I{float} 48 | - default: 90 49 | - B{username} - The username used for http authentication. 50 | - type: I{str} 51 | - default: None 52 | - B{password} - The password used for http authentication. 53 | - type: I{str} 54 | - default: None 55 | """ 56 | HttpTransport.__init__(self, **kwargs) 57 | self.pm = HTTPPasswordMgrWithDefaultRealm() 58 | 59 | def open(self, request): 60 | self.addcredentials(request) 61 | return HttpTransport.open(self, request) 62 | 63 | def send(self, request): 64 | self.addcredentials(request) 65 | return HttpTransport.send(self, request) 66 | 67 | def addcredentials(self, request): 68 | credentials = self.credentials() 69 | if not (None in credentials): 70 | u = credentials[0] 71 | p = credentials[1] 72 | self.pm.add_password(None, request.url, u, p) 73 | 74 | def credentials(self): 75 | return (self.options.username, self.options.password) 76 | 77 | def u2handlers(self): 78 | handlers = HttpTransport.u2handlers(self) 79 | handlers.append(HTTPBasicAuthHandler(self.pm)) 80 | return handlers 81 | 82 | 83 | class WindowsHttpAuthenticated(HttpAuthenticated): 84 | """ 85 | Provides Windows (NTLM) http authentication. 86 | @ivar pm: The password manager. 87 | @ivar handler: The authentication handler. 88 | @author: Christopher Bess 89 | """ 90 | 91 | def u2handlers(self): 92 | # try to import ntlm support 93 | try: 94 | from ntlm3 import HTTPNtlmAuthHandler 95 | except ImportError: 96 | raise Exception("Cannot import python-ntlm3 module") 97 | handlers = HttpTransport.u2handlers(self) 98 | handlers.append(HTTPNtlmAuthHandler.HTTPNtlmAuthHandler(self.pm)) 99 | return handlers 100 | -------------------------------------------------------------------------------- /suds/transport/options.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Contains classes for transport options. 19 | """ 20 | 21 | 22 | from suds.properties import Skin, Definition 23 | 24 | 25 | class Options(Skin): 26 | """ 27 | Options: 28 | - B{proxy} - An http proxy to be specified on requests. 29 | The proxy is defined as {protocol:proxy,} 30 | - type: I{dict} 31 | - default: {} 32 | - B{timeout} - Set the url open timeout (seconds). 33 | - type: I{float} 34 | - default: 90 35 | - B{headers} - Extra HTTP headers. 36 | - type: I{dict} 37 | - I{str} B{http} - The I{http} protocol proxy URL. 38 | - I{str} B{https} - The I{https} protocol proxy URL. 39 | - default: {} 40 | - B{username} - The username used for http authentication. 41 | - type: I{str} 42 | - default: None 43 | - B{password} - The password used for http authentication. 44 | - type: I{str} 45 | - default: None 46 | """ 47 | def __init__(self, **kwargs): 48 | domain = __name__ 49 | definitions = [ 50 | Definition('proxy', dict, {}), 51 | Definition('timeout', (int, float), 90), 52 | Definition('headers', dict, {}), 53 | Definition('username', str, None), 54 | Definition('password', str, None), 55 | ] 56 | Skin.__init__(self, domain, definitions, kwargs) 57 | -------------------------------------------------------------------------------- /suds/umx/__init__.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Provides modules containing classes to support 19 | unmarshalling (XML). 20 | """ 21 | 22 | from suds.sudsobject import Object 23 | 24 | 25 | class Content(Object): 26 | """ 27 | @ivar node: The content source node. 28 | @type node: L{sax.element.Element} 29 | @ivar data: The (optional) content data. 30 | @type data: L{Object} 31 | @ivar text: The (optional) content (xml) text. 32 | @type text: basestring 33 | """ 34 | 35 | extensions = [] 36 | 37 | def __init__(self, node, **kwargs): 38 | Object.__init__(self) 39 | self.node = node 40 | self.data = None 41 | self.text = None 42 | for k, v in kwargs.items(): 43 | setattr(self, k, v) 44 | 45 | def __getattr__(self, name): 46 | if name not in self.__dict__: 47 | if name in self.extensions: 48 | v = None 49 | setattr(self, name, v) 50 | else: 51 | raise AttributeError('Content has no attribute %s' % name) 52 | else: 53 | v = self.__dict__[name] 54 | return v 55 | -------------------------------------------------------------------------------- /suds/umx/attrlist.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Provides filtered attribute list classes. 19 | """ 20 | 21 | from suds.sax import Namespace 22 | 23 | 24 | class AttrList: 25 | """ 26 | A filtered attribute list. 27 | Items are included during iteration if they are in either the (xs) or 28 | (xml) namespaces. 29 | @ivar raw: The I{raw} attribute list. 30 | @type raw: list 31 | """ 32 | def __init__(self, attributes): 33 | """ 34 | @param attributes: A list of attributes 35 | @type attributes: list 36 | """ 37 | self.raw = attributes 38 | 39 | def real(self): 40 | """ 41 | Get list of I{real} attributes which exclude xs and xml attributes. 42 | @return: A list of I{real} attributes. 43 | @rtype: I{generator} 44 | """ 45 | for a in self.raw: 46 | if self.skip(a): 47 | continue 48 | yield a 49 | 50 | def rlen(self): 51 | """ 52 | Get the number of I{real} attributes which exclude xs and xml 53 | attributes. 54 | @return: A count of I{real} attributes. 55 | @rtype: L{int} 56 | """ 57 | n = 0 58 | for a in self.real(): 59 | n += 1 60 | return n 61 | 62 | def lang(self): 63 | """ 64 | Get list of I{filtered} attributes which exclude xs. 65 | @return: A list of I{filtered} attributes. 66 | @rtype: I{generator} 67 | """ 68 | for a in self.raw: 69 | if a.qname() == 'xml:lang': 70 | return a.value 71 | return None 72 | 73 | def skip(self, attr): 74 | """ 75 | Get whether to skip (filter-out) the specified attribute. 76 | @param attr: An attribute. 77 | @type attr: I{Attribute} 78 | @return: True if should be skipped. 79 | @rtype: bool 80 | """ 81 | ns = attr.namespace() 82 | skip = ( 83 | Namespace.xmlns[1], 84 | 'http://schemas.xmlsoap.org/soap/encoding/', 85 | 'http://schemas.xmlsoap.org/soap/envelope/', 86 | 'http://www.w3.org/2003/05/soap-envelope', 87 | ) 88 | return Namespace.xs(ns) or ns[1] in skip 89 | -------------------------------------------------------------------------------- /suds/umx/basic.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Provides basic unmarshaller classes. 19 | """ 20 | 21 | from suds.umx import Content 22 | from suds.umx.core import Core 23 | 24 | 25 | class Basic(Core): 26 | """ 27 | A object builder (unmarshaller). 28 | """ 29 | 30 | def process(self, node): 31 | """ 32 | Process an object graph representation of the xml I{node}. 33 | @param node: An XML tree. 34 | @type node: L{sax.element.Element} 35 | @return: A suds object. 36 | @rtype: L{Object} 37 | """ 38 | content = Content(node) 39 | return Core.process(self, content) 40 | -------------------------------------------------------------------------------- /suds/umx/core.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Provides base classes for XML->object I{unmarshalling}. 19 | """ 20 | 21 | from logging import getLogger 22 | from suds.compat import basestring 23 | from suds.umx import Content 24 | from suds.umx.attrlist import AttrList 25 | from suds.sax.text import Text 26 | from suds.sudsobject import Factory, merge 27 | 28 | 29 | log = getLogger(__name__) 30 | 31 | reserved = { 32 | 'class': 'cls', 33 | 'def': 'dfn', 34 | } 35 | 36 | 37 | class Core: 38 | """ 39 | The abstract XML I{node} unmarshaller. This class provides the 40 | I{core} unmarshalling functionality. 41 | """ 42 | 43 | def process(self, content): 44 | """ 45 | Process an object graph representation of the xml I{node}. 46 | @param content: The current content being unmarshalled. 47 | @type content: L{Content} 48 | @return: A suds object. 49 | @rtype: L{Object} 50 | """ 51 | self.reset() 52 | return self.append(content) 53 | 54 | def append(self, content): 55 | """ 56 | Process the specified node and convert the XML document into 57 | a I{suds} L{object}. 58 | @param content: The current content being unmarshalled. 59 | @type content: L{Content} 60 | @return: A I{append-result} tuple as: (L{Object}, I{value}) 61 | @rtype: I{append-result} 62 | @note: This is not the proper entry point. 63 | @see: L{process()} 64 | """ 65 | self.start(content) 66 | self.append_attributes(content) 67 | self.append_children(content) 68 | self.append_text(content) 69 | self.end(content) 70 | return self.postprocess(content) 71 | 72 | def postprocess(self, content): 73 | """ 74 | Perform final processing of the resulting data structure as follows: 75 | - Mixed values (children and text) will have a result of the 76 | I{content.node}. 77 | - Semi-simple values (attributes, no-children and text) will have a 78 | result of a property object. 79 | - Simple values (no-attributes, no-children with text nodes) will 80 | have a string result equal to the value of the 81 | content.node.getText(). 82 | @param content: The current content being unmarshalled. 83 | @type content: L{Content} 84 | @return: The post-processed result. 85 | @rtype: I{any} 86 | """ 87 | node = content.node 88 | if len(node.children) and node.hasText(): 89 | return node 90 | attributes = AttrList(node.attributes) 91 | if ( 92 | attributes.rlen() and 93 | not len(node.children) and 94 | node.hasText() 95 | ): 96 | p = Factory.property(node.name, node.getText()) 97 | return merge(content.data, p) 98 | if len(content.data): 99 | return content.data 100 | lang = attributes.lang() 101 | if content.node.isnil(): 102 | return None 103 | if not len(node.children) and content.text is None: 104 | if self.nillable(content): 105 | return None 106 | else: 107 | return Text('', lang=lang) 108 | if isinstance(content.text, basestring): 109 | return Text(content.text, lang=lang) 110 | else: 111 | return content.text 112 | 113 | def append_attributes(self, content): 114 | """ 115 | Append attribute nodes into L{Content.data}. 116 | Attributes in the I{schema} or I{xml} namespaces are skipped. 117 | @param content: The current content being unmarshalled. 118 | @type content: L{Content} 119 | """ 120 | attributes = AttrList(content.node.attributes) 121 | for attr in attributes.real(): 122 | name = attr.name 123 | value = attr.value 124 | self.append_attribute(name, value, content) 125 | 126 | def append_attribute(self, name, value, content): 127 | """ 128 | Append an attribute name/value into L{Content.data}. 129 | @param name: The attribute name 130 | @type name: basestring 131 | @param value: The attribute's value 132 | @type value: basestring 133 | @param content: The current content being unmarshalled. 134 | @type content: L{Content} 135 | """ 136 | key = name 137 | key = '_%s' % reserved.get(key, key) 138 | setattr(content.data, key, value) 139 | 140 | def append_children(self, content): 141 | """ 142 | Append child nodes into L{Content.data} 143 | @param content: The current content being unmarshalled. 144 | @type content: L{Content} 145 | """ 146 | for child in content.node: 147 | cont = Content(child) 148 | cval = self.append(cont) 149 | key = reserved.get(child.name, child.name) 150 | if key in content.data: 151 | v = getattr(content.data, key) 152 | if isinstance(v, list): 153 | v.append(cval) 154 | else: 155 | setattr(content.data, key, [v, cval]) 156 | continue 157 | if self.unbounded(cont): 158 | if cval is None: 159 | setattr(content.data, key, []) 160 | else: 161 | setattr(content.data, key, [cval, ]) 162 | else: 163 | setattr(content.data, key, cval) 164 | 165 | def append_text(self, content): 166 | """ 167 | Append text nodes into L{Content.data} 168 | @param content: The current content being unmarshalled. 169 | @type content: L{Content} 170 | """ 171 | if content.node.hasText(): 172 | content.text = content.node.getText() 173 | 174 | def reset(self): 175 | pass 176 | 177 | def start(self, content): 178 | """ 179 | Processing on I{node} has started. Build and return 180 | the proper object. 181 | @param content: The current content being unmarshalled. 182 | @type content: L{Content} 183 | @return: A subclass of Object. 184 | @rtype: L{Object} 185 | """ 186 | content.data = Factory.object(content.node.name) 187 | 188 | def end(self, content): 189 | """ 190 | Processing on I{node} has ended. 191 | @param content: The current content being unmarshalled. 192 | @type content: L{Content} 193 | """ 194 | pass 195 | 196 | def bounded(self, content): 197 | """ 198 | Get whether the content is bounded (not a list). 199 | @param content: The current content being unmarshalled. 200 | @type content: L{Content} 201 | @return: True if bounded, else False 202 | @rtype: boolean 203 | '""" 204 | return not self.unbounded(content) 205 | 206 | def unbounded(self, content): 207 | """ 208 | Get whether the object is unbounded (a list). 209 | @param content: The current content being unmarshalled. 210 | @type content: L{Content} 211 | @return: True if unbounded, else False 212 | @rtype: boolean 213 | '""" 214 | return False 215 | 216 | def nillable(self, content): 217 | """ 218 | Get whether the object is nillable. 219 | @param content: The current content being unmarshalled. 220 | @type content: L{Content} 221 | @return: True if nillable, else False 222 | @rtype: boolean 223 | '""" 224 | return False 225 | -------------------------------------------------------------------------------- /suds/umx/encoded.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Provides soap encoded unmarshaller classes. 19 | """ 20 | 21 | from logging import getLogger 22 | from suds.umx import Content 23 | from suds.umx.typed import Typed 24 | from suds.sax import Namespace 25 | 26 | log = getLogger(__name__) 27 | 28 | # 29 | # Add encoded extensions 30 | # aty = The soap (section 5) encoded array type. 31 | # 32 | Content.extensions.append('aty') 33 | 34 | 35 | class Encoded(Typed): 36 | """ 37 | A SOAP section (5) encoding unmarshaller. 38 | This marshaller supports rpc/encoded soap styles. 39 | """ 40 | 41 | def start(self, content): 42 | # 43 | # Grab the array type and continue 44 | # 45 | self.setaty(content) 46 | Typed.start(self, content) 47 | 48 | def end(self, content): 49 | # 50 | # Squash soap encoded arrays into python lists. This is 51 | # also where we insure that empty arrays are represented 52 | # as empty python lists. 53 | # 54 | aty = content.aty 55 | if aty is not None: 56 | self.promote(content) 57 | return Typed.end(self, content) 58 | 59 | def postprocess(self, content): 60 | # 61 | # Ensure proper rendering of empty arrays. 62 | # 63 | if content.aty is None: 64 | return Typed.postprocess(self, content) 65 | else: 66 | return content.data 67 | 68 | def setaty(self, content): 69 | """ 70 | Grab the (aty) soap-enc:arrayType and attach it to the 71 | content for proper array processing later in end(). 72 | @param content: The current content being unmarshalled. 73 | @type content: L{Content} 74 | @return: self 75 | @rtype: L{Encoded} 76 | """ 77 | name = 'arrayType' 78 | ns = (None, 'http://schemas.xmlsoap.org/soap/encoding/') 79 | aty = content.node.get(name, ns) 80 | if aty is not None: 81 | content.aty = aty 82 | parts = aty.split('[') 83 | ref = parts[0] 84 | if len(parts) == 2: 85 | self.applyaty(content, ref) 86 | else: 87 | pass # (2) dimensional array 88 | return self 89 | 90 | def applyaty(self, content, xty): 91 | """ 92 | Apply the type referenced in the I{arrayType} to the content 93 | (child nodes) of the array. Each element (node) in the array 94 | that does not have an explicit xsi:type attribute is given one 95 | based on the I{arrayType}. 96 | @param content: An array content. 97 | @type content: L{Content} 98 | @param xty: The XSI type reference. 99 | @type xty: str 100 | @return: self 101 | @rtype: L{Encoded} 102 | """ 103 | name = 'type' 104 | ns = Namespace.xsins 105 | parent = content.node 106 | for child in parent.getChildren(): 107 | ref = child.get(name, ns) 108 | if ref is None: 109 | parent.addPrefix(ns[0], ns[1]) 110 | attr = ':'.join((ns[0], name)) 111 | child.set(attr, xty) 112 | return self 113 | 114 | def promote(self, content): 115 | """ 116 | Promote (replace) the content.data with the first attribute 117 | of the current content.data that is a I{list}. Note: the 118 | content.data may be empty or contain only _x attributes. 119 | In either case, the content.data is assigned an empty list. 120 | @param content: An array content. 121 | @type content: L{Content} 122 | """ 123 | for n, v in content.data: 124 | if isinstance(v, list): 125 | content.data = v 126 | return 127 | content.data = [] 128 | -------------------------------------------------------------------------------- /suds/umx/typed.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | Provides typed unmarshaller classes. 19 | """ 20 | 21 | from logging import getLogger 22 | from suds import TypeNotFound 23 | from suds.umx import Content 24 | from suds.umx.core import Core 25 | from suds.resolver import NodeResolver, Frame 26 | from suds.sudsobject import Factory 27 | 28 | log = getLogger(__name__) 29 | 30 | 31 | # 32 | # Add typed extensions 33 | # type = The expected xsd type 34 | # real = The 'true' XSD type 35 | # 36 | Content.extensions.append('type') 37 | Content.extensions.append('real') 38 | 39 | 40 | class Typed(Core): 41 | """ 42 | A I{typed} XML unmarshaller 43 | @ivar resolver: A schema type resolver. 44 | @type resolver: L{NodeResolver} 45 | """ 46 | 47 | def __init__(self, schema): 48 | """ 49 | @param schema: A schema object. 50 | @type schema: L{xsd.schema.Schema} 51 | """ 52 | self.resolver = NodeResolver(schema) 53 | 54 | def process(self, node, type): 55 | """ 56 | Process an object graph representation of the xml L{node}. 57 | @param node: An XML tree. 58 | @type node: L{sax.element.Element} 59 | @param type: The I{optional} schema type. 60 | @type type: L{xsd.sxbase.SchemaObject} 61 | @return: A suds object. 62 | @rtype: L{Object} 63 | """ 64 | content = Content(node) 65 | content.type = type 66 | return Core.process(self, content) 67 | 68 | def reset(self): 69 | log.debug('reset') 70 | self.resolver.reset() 71 | 72 | def start(self, content): 73 | # 74 | # Resolve to the schema type; build an object and setup metadata. 75 | # 76 | if content.type is None: 77 | found = self.resolver.find(content.node) 78 | if found is None: 79 | log.error(self.resolver.schema) 80 | raise TypeNotFound(content.node.qname()) 81 | content.type = found 82 | else: 83 | known = self.resolver.known(content.node) 84 | frame = Frame(content.type, resolved=known) 85 | self.resolver.push(frame) 86 | real = self.resolver.top().resolved 87 | content.real = real 88 | cls_name = real.name 89 | if cls_name is None: 90 | cls_name = content.node.name 91 | content.data = Factory.object(cls_name) 92 | md = content.data.__metadata__ 93 | md.sxtype = real 94 | 95 | def end(self, content): 96 | self.resolver.pop() 97 | 98 | def unbounded(self, content): 99 | return content.type.unbounded() 100 | 101 | def nillable(self, content): 102 | resolved = content.type.resolve() 103 | return ( 104 | content.type.nillable or 105 | (resolved.builtin() and resolved.nillable) 106 | ) 107 | 108 | def append_attribute(self, name, value, content): 109 | """ 110 | Append an attribute name/value into L{Content.data}. 111 | @param name: The attribute name 112 | @type name: basestring 113 | @param value: The attribute's value 114 | @type value: basestring 115 | @param content: The current content being unmarshalled. 116 | @type content: L{Content} 117 | """ 118 | type = self.resolver.findattr(name) 119 | if type is None: 120 | log.warn('attribute (%s) type, not-found', name) 121 | else: 122 | value = self.translated(value, type) 123 | Core.append_attribute(self, name, value, content) 124 | 125 | def append_text(self, content): 126 | """ 127 | Append text nodes into L{Content.data} 128 | Here is where the I{true} type is used to translate the value 129 | into the proper python type. 130 | @param content: The current content being unmarshalled. 131 | @type content: L{Content} 132 | """ 133 | Core.append_text(self, content) 134 | known = self.resolver.top().resolved 135 | content.text = self.translated(content.text, known) 136 | 137 | def translated(self, value, type): 138 | """ translate using the schema type """ 139 | if value is not None: 140 | resolved = type.resolve() 141 | return resolved.translate(value) 142 | else: 143 | return value 144 | -------------------------------------------------------------------------------- /suds/utils.py: -------------------------------------------------------------------------------- 1 | 2 | def is_builtin(name): 3 | return name.startswith('__') and name.endswith('__') 4 | -------------------------------------------------------------------------------- /suds/wsse.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | The I{wsse} module provides WS-Security. 19 | """ 20 | 21 | from suds.sudsobject import Object 22 | from suds.sax.element import Element 23 | from suds.sax.date import DateTime, UtcTimezone 24 | from datetime import datetime, timedelta 25 | 26 | try: 27 | from hashlib import sha256 28 | except ImportError: 29 | # Python 2.4 compatibility 30 | from md5 import md5 as sha256 31 | 32 | 33 | 34 | dsns = ('ds', 'http://www.w3.org/2000/09/xmldsig#') 35 | wssens = ( 36 | 'wsse', 37 | 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' 38 | ) 39 | wsuns = ( 40 | 'wsu', 41 | 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' 42 | ) 43 | wsencns = ( 44 | 'wsenc', 45 | 'http://www.w3.org/2001/04/xmlenc#' 46 | ) 47 | 48 | 49 | class Security(Object): 50 | """ 51 | WS-Security object. 52 | @ivar tokens: A list of security tokens 53 | @type tokens: [L{Token},...] 54 | @ivar signatures: A list of signatures. 55 | @type signatures: TBD 56 | @ivar references: A list of references. 57 | @type references: TBD 58 | @ivar keys: A list of encryption keys. 59 | @type keys: TBD 60 | """ 61 | 62 | def __init__(self): 63 | """ """ 64 | Object.__init__(self) 65 | self.mustUnderstand = True 66 | self.tokens = [] 67 | self.signatures = [] 68 | self.references = [] 69 | self.keys = [] 70 | 71 | def xml(self): 72 | """ 73 | Get xml representation of the object. 74 | @return: The root node. 75 | @rtype: L{Element} 76 | """ 77 | root = Element('Security', ns=wssens) 78 | root.set('mustUnderstand', str(self.mustUnderstand).lower()) 79 | for t in self.tokens: 80 | root.append(t.xml()) 81 | return root 82 | 83 | 84 | class Token(Object): 85 | """ I{Abstract} security token. """ 86 | 87 | @classmethod 88 | def now(cls): 89 | return datetime.now() 90 | 91 | @classmethod 92 | def utc(cls): 93 | return datetime.utcnow().replace(tzinfo=UtcTimezone()) 94 | 95 | @classmethod 96 | def sysdate(cls): 97 | utc = DateTime(cls.utc()) 98 | return str(utc) 99 | 100 | def __init__(self): 101 | Object.__init__(self) 102 | 103 | 104 | class UsernameToken(Token): 105 | """ 106 | Represents a basic I{UsernameToken} WS-Secuirty token. 107 | @ivar username: A username. 108 | @type username: str 109 | @ivar password: A password. 110 | @type password: str 111 | @ivar nonce: A set of bytes to prevent reply attacks. 112 | @type nonce: str 113 | @ivar created: The token created. 114 | @type created: L{datetime} 115 | """ 116 | 117 | def __init__(self, username=None, password=None): 118 | """ 119 | @param username: A username. 120 | @type username: str 121 | @param password: A password. 122 | @type password: str 123 | """ 124 | Token.__init__(self) 125 | self.username = username 126 | self.password = password 127 | self.nonce = None 128 | self.created = None 129 | 130 | def setnonce(self, text=None): 131 | """ 132 | Set I{nonce} which is arbitraty set of bytes to prevent 133 | reply attacks. 134 | @param text: The nonce text value. 135 | Generated when I{None}. 136 | @type text: str 137 | """ 138 | if text is None: 139 | s = [] 140 | s.append(self.username) 141 | s.append(self.password) 142 | s.append(Token.sysdate()) 143 | m = sha256() 144 | m.update(':'.join(s).encode("utf-8")) 145 | self.nonce = m.hexdigest() 146 | else: 147 | self.nonce = text 148 | 149 | def setcreated(self, dt=None): 150 | """ 151 | Set I{created}. 152 | @param dt: The created date & time. 153 | Set as datetime.utc() when I{None}. 154 | @type dt: L{datetime} 155 | """ 156 | if dt is None: 157 | self.created = Token.utc() 158 | else: 159 | self.created = dt 160 | 161 | def xml(self): 162 | """ 163 | Get xml representation of the object. 164 | @return: The root node. 165 | @rtype: L{Element} 166 | """ 167 | root = Element('UsernameToken', ns=wssens) 168 | u = Element('Username', ns=wssens) 169 | u.setText(self.username) 170 | root.append(u) 171 | p = Element('Password', ns=wssens) 172 | p.setText(self.password) 173 | root.append(p) 174 | if self.nonce is not None: 175 | n = Element('Nonce', ns=wssens) 176 | n.setText(self.nonce) 177 | root.append(n) 178 | if self.created is not None: 179 | n = Element('Created', ns=wsuns) 180 | n.setText(str(DateTime(self.created))) 181 | root.append(n) 182 | return root 183 | 184 | 185 | class Timestamp(Token): 186 | """ 187 | Represents the I{Timestamp} WS-Secuirty token. 188 | @ivar created: The token created. 189 | @type created: L{datetime} 190 | @ivar expires: The token expires. 191 | @type expires: L{datetime} 192 | """ 193 | 194 | def __init__(self, validity=90): 195 | """ 196 | @param validity: The time in seconds. 197 | @type validity: int 198 | """ 199 | Token.__init__(self) 200 | self.created = Token.utc() 201 | self.expires = self.created + timedelta(seconds=validity) 202 | 203 | def xml(self): 204 | root = Element("Timestamp", ns=wsuns) 205 | created = Element('Created', ns=wsuns) 206 | created.setText(str(DateTime(self.created))) 207 | expires = Element('Expires', ns=wsuns) 208 | expires.setText(str(DateTime(self.expires))) 209 | root.append(created) 210 | root.append(expires) 211 | return root 212 | -------------------------------------------------------------------------------- /suds/xsd/__init__.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | The I{schema} module provides a intelligent representation of 19 | an XSD schema. The I{raw} model is the XML tree and the I{model} 20 | is the denormalized, objectified and intelligent view of the schema. 21 | Most of the I{value-add} provided by the model is centered around 22 | tranparent referenced type resolution and targeted denormalization. 23 | """ 24 | 25 | from logging import getLogger 26 | from suds.compat import basestring 27 | from suds.sax import Namespace, splitPrefix 28 | 29 | log = getLogger(__name__) 30 | 31 | 32 | def qualify(ref, resolvers, defns=Namespace.default): 33 | """ 34 | Get a reference that is I{qualified} by namespace. 35 | @param ref: A referenced schema type name. 36 | @type ref: str 37 | @param resolvers: A list of objects to be used to resolve types. 38 | @type resolvers: [L{sax.element.Element},] 39 | @param defns: An optional target namespace used to qualify references 40 | when no prefix is specified. 41 | @type defns: A default namespace I{tuple: (prefix,uri)} used when ref not 42 | prefixed. 43 | @return: A qualified reference. 44 | @rtype: (name, namespace-uri) 45 | """ 46 | ns = None 47 | p, n = splitPrefix(ref) 48 | if p is not None: 49 | if not isinstance(resolvers, (list, tuple)): 50 | resolvers = (resolvers,) 51 | for r in resolvers: 52 | resolved = r.resolvePrefix(p) 53 | if resolved[1] is not None: 54 | ns = resolved 55 | break 56 | if ns is None: 57 | raise Exception('prefix (%s) not resolved' % p) 58 | else: 59 | ns = defns 60 | return (n, ns[1]) 61 | 62 | 63 | def isqref(object): 64 | """ 65 | Get whether the object is a I{qualified reference}. 66 | @param object: An object to be tested. 67 | @type object: I{any} 68 | @rtype: boolean 69 | @see: L{qualify} 70 | """ 71 | return ( 72 | isinstance(object, tuple) and 73 | len(object) == 2 and 74 | isinstance(object[0], basestring) and 75 | isinstance(object[1], basestring)) 76 | 77 | 78 | class Filter: 79 | def __init__(self, inclusive=False, *items): 80 | self.inclusive = inclusive 81 | self.items = items 82 | 83 | def __contains__(self, x): 84 | if self.inclusive: 85 | result = x in self.items 86 | else: 87 | result = x not in self.items 88 | return result 89 | -------------------------------------------------------------------------------- /suds/xsd/deplist.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | The I{depsolve} module defines a class for performing dependancy solving. 19 | """ 20 | 21 | from logging import getLogger 22 | 23 | from suds import Repr 24 | 25 | log = getLogger(__name__) 26 | 27 | 28 | class DepList: 29 | """ 30 | Dependancy solving list. 31 | Items are tuples: (object, (deps,)) 32 | @ivar raw: The raw (unsorted) items. 33 | @type raw: list 34 | @ivar index: The index of (unsorted) items. 35 | @type index: list 36 | @ivar stack: The sorting stack. 37 | @type stack: list 38 | @ivar pushed: The I{pushed} set tracks items that have been 39 | processed. 40 | @type pushed: set 41 | @ivar sorted: The sorted list of items. 42 | @type sorted: list 43 | """ 44 | 45 | def __init__(self): 46 | """ """ 47 | self.unsorted = [] 48 | self.index = {} 49 | self.stack = [] 50 | self.pushed = set() 51 | self.sorted = None 52 | 53 | def add(self, *items): 54 | """ 55 | Add items to be sorted. 56 | @param items: One or more items to be added. 57 | @type items: I{item} 58 | @return: self 59 | @rtype: L{DepList} 60 | """ 61 | for item in items: 62 | self.unsorted.append(item) 63 | key = item[0] 64 | self.index[key] = item 65 | return self 66 | 67 | def sort(self): 68 | """ 69 | Sort the list based on dependancies. 70 | @return: The sorted items. 71 | @rtype: list 72 | """ 73 | self.sorted = list() 74 | self.pushed = set() 75 | for item in self.unsorted: 76 | popped = [] 77 | self.push(item) 78 | while len(self.stack): 79 | try: 80 | top = self.top() 81 | ref = next(top[1]) 82 | refd = self.index.get(ref) 83 | if refd is None: 84 | log.debug('"%s" not found, skipped', Repr(ref)) 85 | continue 86 | self.push(refd) 87 | except StopIteration: 88 | popped.append(self.pop()) 89 | continue 90 | for p in popped: 91 | self.sorted.append(p) 92 | self.unsorted = self.sorted 93 | return self.sorted 94 | 95 | def top(self): 96 | """ 97 | Get the item at the top of the stack. 98 | @return: The top item. 99 | @rtype: (item, iter) 100 | """ 101 | return self.stack[-1] 102 | 103 | def push(self, item): 104 | """ 105 | Push and item onto the sorting stack. 106 | @param item: An item to push. 107 | @type item: I{item} 108 | @return: The number of items pushed. 109 | @rtype: int 110 | """ 111 | if item in self.pushed: 112 | return 113 | frame = (item, iter(item[1])) 114 | self.stack.append(frame) 115 | self.pushed.add(item) 116 | 117 | def pop(self): 118 | """ 119 | Pop the top item off the stack and append 120 | it to the sorted list. 121 | @return: The popped item. 122 | @rtype: I{item} 123 | """ 124 | try: 125 | frame = self.stack.pop() 126 | return frame[0] 127 | except: 128 | pass 129 | 130 | 131 | if __name__ == '__main__': 132 | a = ('a', ('x',)) 133 | b = ('b', ('a',)) 134 | c = ('c', ('a', 'b')) 135 | d = ('d', ('c',)) 136 | e = ('e', ('d', 'a')) 137 | f = ('f', ('e', 'c', 'd', 'a')) 138 | x = ('x', ()) 139 | L = DepList() 140 | L.add(c, e, d, b, f, a, x) 141 | print([item[0] for item in L.sort()]) 142 | -------------------------------------------------------------------------------- /suds/xsd/doctor.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | The I{doctor} module provides classes for fixing broken (sick) 19 | schema(s). 20 | """ 21 | 22 | from logging import getLogger 23 | from suds.sax import Namespace 24 | from suds.sax.element import Element 25 | from suds.plugin import DocumentPlugin, DocumentContext 26 | 27 | log = getLogger(__name__) 28 | 29 | 30 | class Doctor: 31 | """ 32 | Schema Doctor. 33 | """ 34 | def examine(self, root): 35 | """ 36 | Examine and repair the schema (if necessary). 37 | @param root: A schema root element. 38 | @type root: L{Element} 39 | """ 40 | pass 41 | 42 | 43 | class Practice(Doctor): 44 | """ 45 | A collection of doctors. 46 | @ivar doctors: A list of doctors. 47 | @type doctors: list 48 | """ 49 | 50 | def __init__(self): 51 | self.doctors = [] 52 | 53 | def add(self, doctor): 54 | """ 55 | Add a doctor to the practice 56 | @param doctor: A doctor to add. 57 | @type doctor: L{Doctor} 58 | """ 59 | self.doctors.append(doctor) 60 | 61 | def examine(self, root): 62 | for d in self.doctors: 63 | d.examine(root) 64 | return root 65 | 66 | 67 | class TnsFilter: 68 | """ 69 | Target Namespace filter. 70 | @ivar tns: A list of target namespaces. 71 | @type tns: [str,...] 72 | """ 73 | 74 | def __init__(self, *tns): 75 | """ 76 | @param tns: A list of target namespaces. 77 | @type tns: [str,...] 78 | """ 79 | self.tns = [] 80 | self.add(*tns) 81 | 82 | def add(self, *tns): 83 | """ 84 | Add I{targetNamesapces} to be added. 85 | @param tns: A list of target namespaces. 86 | @type tns: [str,...] 87 | """ 88 | self.tns += tns 89 | 90 | def match(self, root, ns): 91 | """ 92 | Match by I{targetNamespace} excluding those that 93 | are equal to the specified namespace to prevent 94 | adding an import to itself. 95 | @param root: A schema root. 96 | @type root: L{Element} 97 | """ 98 | tns = root.get('targetNamespace') 99 | if len(self.tns): 100 | matched = tns in self.tns 101 | else: 102 | matched = 1 103 | itself = ns == tns 104 | return matched and not itself 105 | 106 | 107 | class Import: 108 | """ 109 | An to be applied. 110 | @cvar xsdns: The XSD namespace. 111 | @type xsdns: (p,u) 112 | @ivar ns: An import namespace. 113 | @type ns: str 114 | @ivar location: An optional I{schemaLocation}. 115 | @type location: str 116 | @ivar filter: A filter used to restrict application to 117 | a particular schema. 118 | @type filter: L{TnsFilter} 119 | """ 120 | 121 | xsdns = Namespace.xsdns 122 | 123 | def __init__(self, ns, location=None): 124 | """ 125 | @param ns: An import namespace. 126 | @type ns: str 127 | @param location: An optional I{schemaLocation}. 128 | @type location: str 129 | """ 130 | self.ns = ns 131 | self.location = location 132 | self.filter = TnsFilter() 133 | 134 | def setfilter(self, filter): 135 | """ 136 | Set the filter. 137 | @param filter: A filter to set. 138 | @type filter: L{TnsFilter} 139 | """ 140 | self.filter = filter 141 | 142 | def apply(self, root): 143 | """ 144 | Apply the import (rule) to the specified schema. 145 | If the schema does not already contain an import for the 146 | I{namespace} specified here, it is added. 147 | @param root: A schema root. 148 | @type root: L{Element} 149 | """ 150 | if not self.filter.match(root, self.ns): 151 | return 152 | if self.exists(root): 153 | return 154 | node = Element('import', ns=self.xsdns) 155 | node.set('namespace', self.ns) 156 | if self.location is not None: 157 | node.set('schemaLocation', self.location) 158 | log.debug('inserting: %s', node) 159 | root.insert(node) 160 | 161 | def add(self, root): 162 | """ 163 | Add an to the specified schema root. 164 | @param root: A schema root. 165 | @type root: L{Element} 166 | """ 167 | node = Element('import', ns=self.xsdns) 168 | node.set('namespace', self.ns) 169 | if self.location is not None: 170 | node.set('schemaLocation', self.location) 171 | log.debug('%s inserted', node) 172 | root.insert(node) 173 | 174 | def exists(self, root): 175 | """ 176 | Check to see if the already exists 177 | in the specified schema root by matching I{namesapce}. 178 | @param root: A schema root. 179 | @type root: L{Element} 180 | """ 181 | for node in root.children: 182 | if node.name != 'import': 183 | continue 184 | ns = node.get('namespace') 185 | if self.ns == ns: 186 | return 1 187 | return 0 188 | 189 | 190 | class ImportDoctor(Doctor, DocumentPlugin): 191 | """ 192 | Doctor used to fix missing imports. 193 | @ivar imports: A list of imports to apply. 194 | @type imports: [L{Import},...] 195 | """ 196 | 197 | def __init__(self, *imports): 198 | """ 199 | """ 200 | self.imports = [] 201 | self.add(*imports) 202 | 203 | def add(self, *imports): 204 | """ 205 | Add a namesapce to be checked. 206 | @param imports: A list of L{Import} objects. 207 | @type imports: [L{Import},..] 208 | """ 209 | self.imports += imports 210 | 211 | def examine(self, node): 212 | for imp in self.imports: 213 | imp.apply(node) 214 | 215 | def parsed(self, context): 216 | node = context.document 217 | # xsd root 218 | if node.name == 'schema' and Namespace.xsd(node.namespace()): 219 | self.examine(node) 220 | return 221 | # look deeper 222 | context = DocumentContext() 223 | for child in node: 224 | context.document = child 225 | self.parsed(context) 226 | -------------------------------------------------------------------------------- /suds/xsd/query.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | The I{query} module defines a class for performing schema queries. 19 | """ 20 | 21 | from logging import getLogger 22 | from suds import Object, Repr, objid, tostr 23 | from suds.xsd import qualify, isqref 24 | from suds.xsd.sxbuiltin import Factory 25 | 26 | log = getLogger(__name__) 27 | 28 | 29 | class Query(Object): 30 | """ 31 | Schema query base class. 32 | """ 33 | 34 | def __init__(self, ref=None): 35 | """ 36 | @param ref: The schema reference being queried. 37 | @type ref: qref 38 | """ 39 | Object.__init__(self) 40 | self.id = objid(self) 41 | self.ref = ref 42 | self.history = [] 43 | self.resolved = False 44 | if not isqref(self.ref): 45 | raise Exception('%s, must be qref' % tostr(self.ref)) 46 | 47 | def execute(self, schema): 48 | """ 49 | Execute this query using the specified schema. 50 | @param schema: The schema associated with the query. The schema 51 | is used by the query to search for items. 52 | @type schema: L{schema.Schema} 53 | @return: The item matching the search criteria. 54 | @rtype: L{sxbase.SchemaObject} 55 | """ 56 | raise Exception('not-implemented by subclass') 57 | 58 | def filter(self, result): 59 | """ 60 | Filter the specified result based on query criteria. 61 | @param result: A potential result. 62 | @type result: L{sxbase.SchemaObject} 63 | @return: True if result should be excluded. 64 | @rtype: boolean 65 | """ 66 | if result is None: 67 | return True 68 | reject = result in self.history 69 | if reject: 70 | log.debug('result %s, rejected by\n%s', Repr(result), self) 71 | return reject 72 | 73 | def result(self, result): 74 | """ 75 | Query result post processing. 76 | @param result: A query result. 77 | @type result: L{sxbase.SchemaObject} 78 | """ 79 | if result is None: 80 | log.debug('%s, not-found', self.ref) 81 | return 82 | if self.resolved: 83 | result = result.resolve() 84 | log.debug('%s, found as: %s', self.ref, Repr(result)) 85 | self.history.append(result) 86 | return result 87 | 88 | 89 | class BlindQuery(Query): 90 | """ 91 | Schema query class that I{blindly} searches for a reference in 92 | the specified schema. It may be used to find Elements and Types but 93 | will match on an Element first. This query will also find builtins. 94 | """ 95 | 96 | def execute(self, schema): 97 | if schema.builtin(self.ref): 98 | name = self.ref[0] 99 | b = Factory.create(schema, name) 100 | log.debug('%s, found builtin (%s)', self.id, name) 101 | return b 102 | result = None 103 | for d in (schema.elements, schema.types): 104 | result = d.get(self.ref) 105 | if self.filter(result): 106 | result = None 107 | else: 108 | break 109 | if result is None: 110 | eq = ElementQuery(self.ref) 111 | eq.history = self.history 112 | result = eq.execute(schema) 113 | return self.result(result) 114 | 115 | 116 | class TypeQuery(Query): 117 | """ 118 | Schema query class that searches for Type references in 119 | the specified schema. Matches on root types only. 120 | """ 121 | 122 | def execute(self, schema): 123 | if schema.builtin(self.ref): 124 | name = self.ref[0] 125 | b = Factory.create(schema, name) 126 | log.debug('%s, found builtin (%s)', self.id, name) 127 | return b 128 | result = schema.types.get(self.ref) 129 | if self.filter(result): 130 | result = None 131 | return self.result(result) 132 | 133 | 134 | class GroupQuery(Query): 135 | """ 136 | Schema query class that searches for Group references in 137 | the specified schema. 138 | """ 139 | 140 | def execute(self, schema): 141 | result = schema.groups.get(self.ref) 142 | if self.filter(result): 143 | result = None 144 | return self.result(result) 145 | 146 | 147 | class AttrQuery(Query): 148 | """ 149 | Schema query class that searches for Attribute references in the specified 150 | schema. Matches on root Attribute by qname first, then searches deep into 151 | the document. 152 | """ 153 | 154 | def execute(self, schema): 155 | result = schema.attributes.get(self.ref) 156 | if self.filter(result): 157 | result = self.__deepsearch(schema) 158 | return self.result(result) 159 | 160 | def __deepsearch(self, schema): 161 | from suds.xsd.sxbasic import Attribute 162 | result = None 163 | for e in schema.all: 164 | result = e.find(self.ref, (Attribute,)) 165 | if self.filter(result): 166 | result = None 167 | else: 168 | break 169 | return result 170 | 171 | 172 | class AttrGroupQuery(Query): 173 | """ 174 | Schema query class that searches for attributeGroup references in 175 | the specified schema. 176 | """ 177 | 178 | def execute(self, schema): 179 | result = schema.agrps.get(self.ref) 180 | if self.filter(result): 181 | result = None 182 | return self.result(result) 183 | 184 | 185 | class ElementQuery(Query): 186 | """ 187 | Schema query class that searches for Element references in the specified 188 | schema. Matches on root Elements by qname first, then searches deep into 189 | the document. 190 | """ 191 | 192 | def execute(self, schema): 193 | result = schema.elements.get(self.ref) 194 | if self.filter(result): 195 | result = self.__deepsearch(schema) 196 | return self.result(result) 197 | 198 | def __deepsearch(self, schema): 199 | from suds.xsd.sxbasic import Element 200 | result = None 201 | for e in schema.all: 202 | result = e.find(self.ref, (Element,)) 203 | if self.filter(result): 204 | result = None 205 | else: 206 | break 207 | return result 208 | -------------------------------------------------------------------------------- /suds/xsd/sxbuiltin.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the (LGPL) GNU Lesser General Public License as 3 | # published by the Free Software Foundation; either version 3 of the 4 | # License, or (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, 7 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | # GNU Library Lesser General Public License for more details at 10 | # ( http://www.gnu.org/licenses/lgpl.html ). 11 | # 12 | # You should have received a copy of the GNU Lesser General Public License 13 | # along with this program; if not, write to the Free Software 14 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 15 | # written by: Jeff Ortel ( jortel@redhat.com ) 16 | 17 | """ 18 | The I{sxbuiltin} module provides classes that represent 19 | XSD I{builtin} schema objects. 20 | """ 21 | 22 | from logging import getLogger 23 | from suds.compat import basestring 24 | from suds.xsd.sxbase import XBuiltin 25 | import suds.sax.date as dt 26 | import datetime 27 | 28 | 29 | log = getLogger(__name__) 30 | 31 | 32 | class XString(XBuiltin): 33 | """ 34 | Represents an (xsd) node 35 | """ 36 | pass 37 | 38 | 39 | class XAny(XBuiltin): 40 | """ 41 | Represents an (xsd) node 42 | """ 43 | 44 | def __init__(self, schema, name): 45 | XBuiltin.__init__(self, schema, name) 46 | self.nillable = False 47 | 48 | def get_child(self, name): 49 | child = XAny(self.schema, name) 50 | return (child, []) 51 | 52 | def any(self): 53 | return True 54 | 55 | 56 | class XBoolean(XBuiltin): 57 | """ 58 | Represents an (xsd) boolean builtin type. 59 | """ 60 | 61 | translation = ( 62 | { 63 | '1': True, 64 | 'true': True, 65 | '0': False, 66 | 'false': False 67 | }, 68 | { 69 | True: 'true', 70 | 1: 'true', 71 | False: 'false', 72 | 0: 'false' 73 | }, 74 | ) 75 | 76 | def translate(self, value, topython=True): 77 | if topython: 78 | if isinstance(value, basestring): 79 | return XBoolean.translation[0].get(value) 80 | else: 81 | return None 82 | else: 83 | if isinstance(value, (bool, int)): 84 | return XBoolean.translation[1].get(value) 85 | else: 86 | return value 87 | 88 | 89 | class XInteger(XBuiltin): 90 | """ 91 | Represents an (xsd) xs:int builtin type. 92 | """ 93 | 94 | def translate(self, value, topython=True): 95 | if topython: 96 | if isinstance(value, basestring) and len(value): 97 | return int(value) 98 | else: 99 | return None 100 | else: 101 | if isinstance(value, int): 102 | return str(value) 103 | else: 104 | return value 105 | 106 | 107 | class XLong(XBuiltin): 108 | """ 109 | Represents an (xsd) xs:long builtin type. 110 | """ 111 | 112 | def translate(self, value, topython=True): 113 | if topython: 114 | if isinstance(value, basestring) and len(value): 115 | return int(value) 116 | else: 117 | return None 118 | else: 119 | if isinstance(value, int): 120 | return str(value) 121 | else: 122 | return value 123 | 124 | 125 | class XFloat(XBuiltin): 126 | """ 127 | Represents an (xsd) xs:float builtin type. 128 | """ 129 | 130 | def translate(self, value, topython=True): 131 | if topython: 132 | if isinstance(value, basestring) and len(value): 133 | return float(value) 134 | else: 135 | return None 136 | else: 137 | if isinstance(value, float): 138 | return str(value) 139 | else: 140 | return value 141 | 142 | 143 | class XDate(XBuiltin): 144 | """ 145 | Represents an (xsd) xs:date builtin type. 146 | """ 147 | 148 | def translate(self, value, topython=True): 149 | if topython: 150 | if isinstance(value, basestring) and len(value): 151 | return dt.Date(value).value 152 | else: 153 | return None 154 | else: 155 | if isinstance(value, datetime.date): 156 | return str(dt.Date(value)) 157 | else: 158 | return value 159 | 160 | 161 | class XTime(XBuiltin): 162 | """ 163 | Represents an (xsd) xs:time builtin type. 164 | """ 165 | 166 | def translate(self, value, topython=True): 167 | if topython: 168 | if isinstance(value, basestring) and len(value): 169 | return dt.Time(value).value 170 | else: 171 | return None 172 | else: 173 | if isinstance(value, datetime.time): 174 | return str(dt.Time(value)) 175 | else: 176 | return value 177 | 178 | 179 | class XDateTime(XBuiltin): 180 | """ 181 | Represents an (xsd) xs:datetime builtin type. 182 | """ 183 | 184 | def translate(self, value, topython=True): 185 | if topython: 186 | if isinstance(value, basestring) and len(value): 187 | return dt.DateTime(value).value 188 | else: 189 | return None 190 | else: 191 | if isinstance(value, datetime.datetime): 192 | return str(dt.DateTime(value)) 193 | else: 194 | return value 195 | 196 | 197 | class Factory: 198 | 199 | tags = { 200 | # any 201 | 'anyType': XAny, 202 | # strings 203 | 'string': XString, 204 | 'normalizedString': XString, 205 | 'ID': XString, 206 | 'Name': XString, 207 | 'QName': XString, 208 | 'NCName': XString, 209 | 'anySimpleType': XString, 210 | 'anyURI': XString, 211 | 'NOTATION': XString, 212 | 'token': XString, 213 | 'language': XString, 214 | 'IDREFS': XString, 215 | 'ENTITIES': XString, 216 | 'IDREF': XString, 217 | 'ENTITY': XString, 218 | 'NMTOKEN': XString, 219 | 'NMTOKENS': XString, 220 | # binary 221 | 'hexBinary': XString, 222 | 'base64Binary': XString, 223 | # integers 224 | 'int': XInteger, 225 | 'integer': XInteger, 226 | 'unsignedInt': XInteger, 227 | 'positiveInteger': XInteger, 228 | 'negativeInteger': XInteger, 229 | 'nonPositiveInteger': XInteger, 230 | 'nonNegativeInteger': XInteger, 231 | # longs 232 | 'long': XLong, 233 | 'unsignedLong': XLong, 234 | # shorts 235 | 'short': XInteger, 236 | 'unsignedShort': XInteger, 237 | 'byte': XInteger, 238 | 'unsignedByte': XInteger, 239 | # floats 240 | 'float': XFloat, 241 | 'double': XFloat, 242 | 'decimal': XFloat, 243 | # dates & times 244 | 'date': XDate, 245 | 'time': XTime, 246 | 'dateTime': XDateTime, 247 | 'duration': XString, 248 | 'gYearMonth': XString, 249 | 'gYear': XString, 250 | 'gMonthDay': XString, 251 | 'gDay': XString, 252 | 'gMonth': XString, 253 | # boolean 254 | 'boolean': XBoolean, 255 | } 256 | 257 | @classmethod 258 | def maptag(cls, tag, fn): 259 | """ 260 | Map (override) tag => I{class} mapping. 261 | @param tag: An xsd tag name. 262 | @type tag: str 263 | @param fn: A function or class. 264 | @type fn: fn|class. 265 | """ 266 | cls.tags[tag] = fn 267 | 268 | @classmethod 269 | def create(cls, schema, name): 270 | """ 271 | Create an object based on the root tag name. 272 | @param schema: A schema object. 273 | @type schema: L{schema.Schema} 274 | @param name: The name. 275 | @type name: str 276 | @return: The created object. 277 | @rtype: L{XBuiltin} 278 | """ 279 | fn = cls.tags.get(name) 280 | if fn is not None: 281 | return fn(schema, name) 282 | else: 283 | return XBuiltin(schema, name) 284 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cackharot/suds-py3/1d92cc6297efee31bfd94b50b99c431505d7de21/tests/__init__.py -------------------------------------------------------------------------------- /tests/cache_test.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import os 3 | import os.path 4 | import subprocess 5 | import sys 6 | import tempfile 7 | import unittest 8 | 9 | import suds 10 | import suds.cache 11 | 12 | 13 | def _is_empty_cache_folder(folder): 14 | assert os.path.isdir(folder) 15 | 16 | def walkError(error): 17 | unittest.TestCase.fail( 18 | "Error attempting to walk through cache folder contents.") 19 | count = 0 20 | for root, folders, files in os.walk(folder, onerror=walkError): 21 | assert root == folder 22 | return len(folders) == 0 and len(files) == 1 and files[0] == 'version' 23 | return False 24 | 25 | class DummyObjectForTesting: 26 | """Dummy class used for pickling related tests.""" 27 | 28 | def __init__(self, x): 29 | self.x = x 30 | 31 | class TestFileCache(unittest.TestCase): 32 | def test_basic_custom_cache_location(self): 33 | cache_folder = os.path.join( 34 | tempfile.gettempdir(), "custom-cache-loc-test") 35 | cache = suds.cache.FileCache(cache_folder) 36 | assert isinstance(cache, suds.cache.Cache) 37 | value = "some value".encode() 38 | cache.put("test_key", value) 39 | assert cache.get("test_key") == value 40 | assert not _is_empty_cache_folder(cache_folder) 41 | 42 | def test_basic_default_cache_location(self): 43 | cache = suds.cache.FileCache() 44 | assert isinstance(cache, suds.cache.Cache) 45 | value = "some value".encode() 46 | cache.put("test_key", value) 47 | assert cache.get("test_key") == value 48 | assert not _is_empty_cache_folder(cache.location) 49 | 50 | def test_close_leaves_cached_files_behind(self): 51 | val1 = "123523".encode() 52 | val2 = u"€ 的 čćžšđČĆŽŠĐ".encode() 53 | cache_folder1 = os.path.join(tempfile.gettempdir(), "f1") 54 | cache1 = suds.cache.FileCache(cache_folder1) 55 | cache1.put("key1", val1) 56 | cache1.put("key2", val2) 57 | 58 | cache_folder2= os.path.join(tempfile.gettempdir(), "f2") 59 | cache2 = suds.cache.FileCache(cache_folder2) 60 | cache2.put("key3", val1) 61 | cache2.put("key4", val2) 62 | 63 | del cache1 64 | 65 | cache11 = suds.cache.FileCache(cache_folder1) 66 | assert cache11.get("key1") == val1 67 | assert cache11.get("key2") == val2 68 | assert cache2.get("key3") == val1 69 | assert cache2.get("key4") == val2 70 | 71 | class TestObjectCache(unittest.TestCase): 72 | def test_basic(self): 73 | cache_folder = os.path.join( 74 | tempfile.gettempdir(), "custom-cache-loc-object-test") 75 | cache = suds.cache.ObjectCache(cache_folder) 76 | assert isinstance(cache, suds.cache.FileCache) 77 | cache.put("key1", DummyObjectForTesting(1)) 78 | cache.put("key2", DummyObjectForTesting(2)) 79 | read1 = cache.get("key1") 80 | read2 = cache.get("key2") 81 | assert read1.__class__ is DummyObjectForTesting 82 | assert read2.__class__ is DummyObjectForTesting 83 | assert read1.x == 1 84 | assert read2.x == 2 85 | 86 | class TestDocumentCache(unittest.TestCase): 87 | def test_basic(self): 88 | cache_folder = os.path.join( 89 | tempfile.gettempdir(), "custom-cache-loc-document-test") 90 | cache = suds.cache.DocumentCache(cache_folder) 91 | assert isinstance(cache, suds.cache.FileCache) 92 | doc = suds.sax.document.Document() 93 | doc.append(suds.sax.element.Element("TestEle1")) 94 | cache.put("key1", doc) 95 | read1 = cache.get("key1") 96 | assert read1.__class__ is suds.sax.document.Document 97 | -------------------------------------------------------------------------------- /tests/encode_specials_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import suds 4 | 5 | class TestEncodeSpecials(unittest.TestCase): 6 | def test_specials_chars(self): 7 | encoder = suds.sax.Encoder() 8 | assert encoder.needsEncoding('<test') == True # output is True 9 | assert encoder.encode('<test') == '&lt;test' 10 | -------------------------------------------------------------------------------- /tests/servicedefinition_test_skip.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import suds 4 | from suds.client import Client 5 | 6 | class TestServiceDefinition(unittest.TestCase): 7 | def setUp(self): 8 | self.client = Client('http://www.thomas-bayer.com/axis2/services/BLZService?wsdl') 9 | 10 | def test_service_representation(self): 11 | string_rep = str(self.client) 12 | ver = suds.__version__ 13 | build = suds.__build__.split()[1] 14 | expected = """ 15 | Suds ( https://github.com/cackharot/suds-py3 ) version: %s IN build: %s 16 | 17 | Service ( BLZService ) tns="http://thomas-bayer.com/blz/" 18 | Prefixes (1) 19 | ns0 = "http://thomas-bayer.com/blz/" 20 | Ports (2): 21 | (BLZServiceSOAP11port_http) 22 | Methods (1): 23 | getBank(xs:string blz, ) 24 | Types (3): 25 | detailsType 26 | getBankResponseType 27 | getBankType 28 | (BLZServiceSOAP12port_http) 29 | Methods (1): 30 | getBank(xs:string blz, ) 31 | Types (3): 32 | detailsType 33 | getBankResponseType 34 | getBankType 35 | --------------------------------------------------------------------------------""" % (ver, build) 36 | self.assertEqual(string_rep, expected) 37 | 38 | if __name__ == '__main__': 39 | unittest.main() 40 | --------------------------------------------------------------------------------